目录

  1. 泛型
  2. 泛型类
    1. 泛型类的使用
    2. 自定义泛型类
  3. 泛型接口
  4. 泛型方法
  5. 类型变量的限定(泛型边界)
  6. 泛型通配符
    1. ?
    2. ? extends E
    3. ? super E
  7. 泛型的约束和局限性
  8. 泛型类型继承规则
  9. 获取泛型的参数类型
  10. 泛型代码和虚拟机
  11. 总结
  12. 附录

泛型

❓ 什么是泛型?

泛型,指的是一种 「 参数化类型 」,目的是避免强类型语言带来的类型限制,具体的例子如下:

下面是一段使用 python 代码求解两数和的代码

1
2
def sum(a,b):
return a+b

python 本身是一种弱类型语言,这就代表着实现的功能与具体的参数类型无关,上面方法中的 a,b 可以是整数也可以是浮点数,但是这并不影响这个方法所要实现的功能。所以,弱类型语言的好处之一就体现出来了,并不会对具体的类型进行编程,而是对具体的功能进行编程。

然而 Java 是一种强类型语言,这也就使得必须确定每一个方法的参数类型,返回类型,有可能对于两数相加这个功能,具有很多种函数写法。对于简单的相同的功能,却要编码多遍往往是不可忍受的,所以 Java 就引入泛型机制,期望削弱方法对类型的依赖,使开发人员更多的面向功能而不是面向具体实现。

类型参数化的含义

  • 类型明确的工作推迟到创建对象或者调用方法时,才去明确的特殊类型.之后将类型作为一种参数传递给集合,这也是一种代码层面上的复用
  • 泛型中的类型在使用时指定,不需要强制类型转换,保证了程序中的类型安全,类型相关的问题,编译器会进行检查

👍使用泛型的优点

  1. 把运行时期的问题提前到了编译期间
  2. 避免了强制类型转换
  3. 优化了程序设计,明确每种集合元素类型

对于类型参数,程序员可能想要内置(plug in)所有的类,在没有过多的限制以及混乱的错误消息的状态下,做所有的事情。一个泛型程序员的任务就是预测出所用类的未来可能有的所有用途

泛型类

首先会介绍常用的一个泛型类Collection及其子类,随后会介绍如何自定义一个泛型类

泛型类的使用

一个泛型类(generic class) 就是具有一个或多个类型变量的类

1
2
3
4
ArrayList<type>//type只能是引用类型,即只能是类
//泛型前后类型必须一致,即必须写相同的类,就是父子类之间也不允许
ArrayList<String> arrayList = new ArrayList<String>();
Collection<Object> c = new Collection<String>();//会报错,因为泛型前后类型不一致

类型变量必须使用大写字母的形势,并且命名较短

类型变量含义
E元素(Element),多用于 java 集合框架
K关键字(Key)
N数字(Number)
T(U、S)类型(Type)
V值(Value)
1
2
3
4
5
6
7
8
9
10
11
ArrayList<String > arrayList = new ArrayList<String >();
arrayList.add("hello");
arrayList.add("world");
arrayList.add("java");
Iterator<String> it = arrayList.iterator();
while (it.hasNext()){
String s = it.next();
System.out.println(s);
}
//JDK7新特性,泛型推断
ArrayList<String > arrayList2 = new ArrayList<>();

自定义泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class GenericClass<T> {
private T data;

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}

public static void main(String[] args) {
GenericClass<String> genericClass=new GenericClass<>();
genericClass.setData("Generic Class");
System.out.println(genericClass.getData());
}
}

泛型接口

定义一个泛型接口与定义一个泛型类十分相似,具体的代码如下:

1
2
3
interface Inter<T>{
public abstract void show(T t);
}

✨ 将泛型定义在接口上,实现类在实现泛型接口的时候具有以下两种

  1. 已经知道该泛型的明确类型(不常用)
  2. 并不知道泛型的明确类型(常用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//方式一:已经知道该泛型的明确类型
class InterImple implements Inter<String>{
//不常用
@Override
public void show(String str) {
System.out.println("from normal imple"+str);
}
}
//方式二:并不知道泛型的明确类型
class InterImple2<T> implements Inter<T>{
//此时泛型接口的使用与泛型类相同
@Override
public void show(T t) {
System.out.println(t);
}
}
public static void main(String[] args) {
Inter<String> inter = new InterImple2<String>();
inter.show("String");
Inter<Integer> inter2 = new InterImple2<Integer>();
inter2.show(123);
Inter<Boolean> inter3 = new InterImple2<Boolean>();
inter3.show(true);
}

泛型方法

✨ 使用泛型方法具有如下特点:

  • 泛型方法可以定义在普通类中,也可以定义在泛型类中
  • 不用因为相同的功能而重载多个类

声明一个泛型方法具有如下格式:

1
public <T> T show(T t);//public 后的<T>声明使用泛型

下面是一个具体的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ObjectTool{
//将泛型定义在方法上
public <T> void show(T t){
System.out.println(t);
}
}
//show方法可以接受任何一种参数
public static void main(String[] args) {
ObjectTool objectTool = new ObjectTool();
objectTool.<String>show("this is string type");
//不知道形式参数类型,泛型参数可以不用明确指定,编译器会自动识别
objectTool.show("hello world");
objectTool.<Integer>show(100);
objectTool.show(true);
}

类型变量的限定(泛型边界)

在泛型类或方法需要对类型变量加以约束,这就需要使用类型变量的限定符,一个类型变量或通配符可以有多个限定, 例如:T extends Comparable & Serializable

在 Java 中存在两种方式对类型变量进行限定:

  • 对类的限定:public class TypeLimitForClass<T extends List & Serializable>{}
  • 对方法的限定:public static <T extends Comparable<T>> T getMin(T a, T b) {}
1
2
3
4
5
6
7
8
/**
* 限定类型使用extends关键字指定
* 可以使类,接口,类放在前面接口放在后面用&符号分割
* 例如:<T extends ArrayList & Comparable<T> & Serializable>
*/
public static <T extends Comparable<T>> T getMin(T a, T b) {
return (a.compareTo(b) < 0) ? a : b;
}
1
2
3
4
5
6
7
8
9
10
11
12
public class TypeLimitForClass<T extends List & Serializable> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static <T extends Comparable<T>> T getMinListSize(T a, T b) {
return (a.compareTo(b) < 0) ? a : b;
}
}

泛型通配符

主要介绍?,? extends E,? super E这三种泛型通配符

1
2
3
class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}

?

任意的引用类型都可以

1
2
3
Collection<?> c1 = new ArrayList<Animal>();
Collection<?> c2 = new ArrayList<Dog>();
Collection<?> c3 = new ArrayList<Cat>();

? extends E

向下限定,E 及其子类

1
2
3
Collection<? extends Animal> c4 = new ArrayList<Animal>();
Collection<? extends Animal> c5 = new ArrayList<Dog>();
Collection<? extends Animal> c6 = new ArrayList<Object>();//出错,只能是子类

? super E

向上限定

1
2
Collection<? super Animal> c7 = new ArrayList<Object>();
Collection<? super Animal> c8 = new ArrayList<Dog>();//出错,只能是父类

泛型的约束和局限性

✨ 使用泛型的特点(约束性和局限性)

  1. 不能实例化泛型类
  2. 静态变量或方法不能引用泛型类型变量,但是静态泛型方法是可以的
  3. 基本类型无法作为泛型类型
  4. 无法使用 instanceof 关键字或==判断泛型类的类型
  5. 泛型类的原生类型与所传递的泛型无关,无论传递什么类型,原生类是一样的
  6. 泛型数组可以声明但无法实例化
  7. 泛型类不能继承 Exception 或者 Throwable
  8. 不能捕获泛型类型限定的异常但可以将泛型限定的异常抛出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class GenericRestrict1<T> {
static class NormalClass {

}

private T data;

/**
* 不能实例化泛型类
* Type parameter 'T' cannot be instantiated directly
*/
public void setData() {
//this.data = new T();
}

/**
* 静态变量或方法不能引用泛型类型变量
private static T result;
private static T getResult() {
return result;
}
*/

/**
* 静态泛型方法是可以的
*/
private static <K> K getKey(K k) {
return k;
}

public static void main(String[] args) {
NormalClass normalClassA = new NormalClass();
NormalClass normalClassB = new NormalClass();
/**
* 基本类型无法作为泛型类型
GenericRestrict1<int> genericRestrictInt = new GenericRestrict1<>();
*/
GenericRestrict1<Integer> genericRestrictInteger = new GenericRestrict1<>();
GenericRestrict1<String> genericRestrictString = new GenericRestrict1<>();
/**
* 无法使用instanceof关键字判断泛型类的类型
* Illegal generic type for instanceof
if(genericRestrictInteger instanceof GenericRestrict1<Integer>){
return;
}
*/

/**
* 无法使用“==”判断两个泛型类的实例
* Operator '==' cannot be applied to this two instance
if (genericRestrictInteger == genericRestrictString) {
return;
}
*/

/**
* 泛型类的原生类型与所传递的泛型无关,无论传递什么类型,原生类是一样的
*/
System.out.println(normalClassA == normalClassB);//false
System.out.println(genericRestrictInteger == genericRestrictInteger);//
System.out.println(genericRestrictInteger.getClass() == genericRestrictString.getClass()); //true
System.out.println(genericRestrictInteger.getClass());//restrict.GenericRestrict1
System.out.println(genericRestrictString.getClass());//restrict.GenericRestrict1

/**
* 泛型数组可以声明但无法实例化
* Generic array creation
*/
GenericRestrict1<String>[] genericRestrict1s;
// genericRestrict1s = new GenericRestrict1<String>[10];
genericRestrict1s = new GenericRestrict1[10];
genericRestrict1s[0]=genericRestrictString;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class GenericRestrict2 {

private class MyException extends Exception {
}

/**
* 泛型类不能继承Exception或者Throwable
* Generic class may not extend 'java.lang.Throwable'
private class MyGenericException<T> extends Exception {

}
private class MyGenericThrowable<T> extends Throwable {
}
*/

/**
* 不能捕获泛型类型限定的异常
* Cannot catch type parameters
public <T extends Exception> void getException(T t) {
try {

} catch (T e) {

}
}
*/

/**
*可以将泛型限定的异常抛出
*/
public <T extends Throwable> void getException(T t) throws T {
try {

} catch (Exception e) {
throw t;
}
}
}

泛型类型继承规则

✨泛型类间的继承特点

  1. 泛型参数是继承关系的泛型类之间是没有继承关系的
  2. 泛型类可以继承其它泛型类;如: public class ArrayListextends AbstractList
  3. 泛型类的继承关系在使用中同样会受到泛型类型的影响

获取泛型的参数类型

Type 是什么?

Type即,java.lang.reflect.Type, 是 Java 中所有类型的公共高级接口, 代表了 Java 中的所有类型。

Type 体系中类型的包括:数组类型(GenericArrayType)、参数化类型(ParameterizedType)、类型变量(TypeVariable)、通配符类型(WildcardType)、原始类型(Class)、基本类型(Class), 以上这些类型都实现 Type 接口

泛型类型常用类
参数化类型泛型 List、Map
数组类型并不是平时使用的 String[]、byte[],而是带有泛型的数组 T[]
通配符类型指的是<?>, <? extends T>等等
原始类型用户自定义的类、枚举、数组、注解等类型
基本类型java 的基本类型,即 int,float,double 等
1
2
3
4
5
6
7
8
9
10
public interface ParameterizedType extends Type {
// 返回确切的泛型参数, 如Map<String, Integer>返回[String, Integer]
Type[] getActualTypeArguments();

//返回当前class或interface声明的类型, 如List<?>返回List
Type getRawType();

//返回所属类型. 如,当前类型为O<T>.I<S>, 则返回O<T>. 顶级类型将返回null
Type getOwnerType();
}

泛型代码和虚拟机

虚拟机没有泛型类型对象,所有对象都属于普通类。Java 泛型是 Java1.5 之后才引入的,为了向下兼容。Java 采用了 C++完全不同的实现思想。Java 中的泛型更多的看起来像是编译期用的,Java 中泛型在运行期是不可见的,会被擦除为它的上级类型。如果是没有限定的泛型参数类型,就会被替换为 Object

无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型 ( raw type )。原始类型的名字就是删去类型参数后的泛型类型名。擦除(erased)类型变量,并替换为限定类型(无限定的变量用 Object)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Pair<T>
{
private T first;
private T second;
public Pair() { first = null ; second = null ; }
public PairfT first, T second) { this,first = first; this.second = second; }
public T getFirstO { return first; }
public T getSecondO { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}
//T 是无限定变量,直接使用Object替换
public class Pair
{
private Object first;
private Object second;
public Pair(Object first, Object second)
{
this,first = first;
this.second = second;
}
public Object getFirstO { return first; }
public Object getSecondO { return second; }
public void setFirst(Object newValue) { first = newValue; }
public void setSecond(Object newValue) { second = newValue; }
}

🎶 C++ 中每个模板的实例化产生不同的类型,这一现象称为模板代码膨账,Java 不存在这个问题的困扰

❓ 为什么需要有如此麻烦的泛型擦出机制?

擦除主要的正当理由是从非泛化代码到泛化代码的转变过程,以及在不破坏现有类库的情况下,将泛型融入到 Java 语言

❓ 泛型类型参数将擦除到它的第一个边界(它可能会有多个边界),那么class Interval<T extends Serializable & Comparable>会切换那个边界?

上述泛型的定义,原始类型用 Serializable 替换 T,而编译器在必要时要向 Comparable 插入强制类型转换。为了提高效率,应该将标签接口(即没有方法的接口)放在边界列表的末尾

总结

✨ Java 具有如下泛型特点

  • 在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型.传入不同泛型实参的泛型类在内存上仅存在一个,即还是原来的最基本的类型,在逻辑上我们可以理解成多个不同的泛型类型;
  • Java 泛型只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的 class 文件中是不包含任何泛型信息的,泛型信息不会进入到运行时阶段。

🎶Java 泛型转换的事实

  • 虚拟机中没有泛型,只有普通的类和方法
  • 所有的类型参数都用它们的限定类型替换
  • 桥方法被合成来保持多态
  • 为保持类型安全性,必要时插入强制类型转换

附录

Java 泛型详解