泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。使用泛型,意味着编写的代码可以被很多不同类型 的对象所重用。

泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ClassTest<T> {
public T firtsFiled;
public T secondFiled;
public void setFirtsFiled(T data){
firtsFiled = data;
}
public void setSecondFiled(T data){
secondFiled = data;
}
public T getFirtsFiled(){
return firtsFiled;
}
public T getSecondFiled(){
return secondFiled;
}
public static <T> T getStaticData(T data){
return data;
}
}

以上是一个简单的泛型类,T成为类型变量,一般使用大写字母命名。在Java中常用变量E表示集合的元素类型,K和V表示关键字与值的类型,T表示任意类型(约定俗成的用法,事实随便一个字母都行)。
当实例化泛型类型需要用具体类型替代类型变量
例如:

1
2
3
4
ClassTest<String> one = new ClassTest<>();
ClassTest<Integer> two = new ClassTest<>();
one.setFirtsFiled("data1");
two.setFirtsFiled(123);

泛型方法
泛型方法可以定义在普通类或泛型类中,与普通方法不同,泛型方法可以在调用它的时候定义类型变量。
例如 public static <T> T getStaticData(T data) 就是一个泛型方法,在方法的返回值前加上 <T> ,在调用时指定类型变量,如下:

1
2
Integer staticData = ClassTest.getStaticData(9090);
String hello = ClassTest.getStaticData("hello");

类型变量的限制
先看这个代码

1
2
3
public static <T extends Comparable> boolean getMinData(T data){
return data.compareTo(data) > 0 ;
}

之所以在定义泛型方法时给 <T> 继承 Comparable 接口,是因为 data 的类型无法确定,不能保证对象都有 compareTo 方法。
一个类型变量或通配符可以有多个限定,例如 T extends Comparable & Serializable
限定类型用“&”分隔,而逗号用来分隔类型变量。
在Java的继承中,可以根据需要拥有多个接口超类型,但限定中至多有一个类。如果用一个类作为限定,它必须是限定列表中的第一个。(core Java)

类型擦除
在虚拟机中没有泛型类型对象,所有对象都属于普通类。Java中的泛型基本上都是在编译器这个级别实现的,生成的字节码信息中是不包含泛型中的类型信息的。在定义一个泛型类型时, 都会提供一个删去类型参数后的原始类型,擦除类型变量,并替换为限定类型(无限定的变量用Object)。
例如以上的泛型类擦除类型后的原始类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ClassTest {
public Object firtsFiled;
public Object secondFiled;
public void setFirtsFiled(Object data){
firtsFiled = data;
}
public void setSecondFiled(Object data){
secondFiled = data;
}
public Object getFirtsFiled(){
return firtsFiled;
}
public Object getSecondFiled(){
return secondFiled;
}
public static Object getStaticData(Object data){
return data;
}
}

所以,不能存在如此两个方法,编译器会提示错误

1
2
3
4
5
6
public String getFirtsFiled(T a){
return "1";
}
public String getFirtsFiled(Object w){
return "1";
}

通配符类型
在泛型操作中进行参数传递时泛型类型必须匹配才能传递,使用通配符来设置传递参数的类型
例子,其中Man是Peple的子类,不必关心实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SubClass {
public void test(ClassTest<Peple> p){
}
public void transfer(ClassTest<? extends Peple> p ){
}
public static void main(String[] args) {
SubClass sub = new SubClass();
ClassTest<Man> tt = new ClassTest<>();
//sub.test(tt);错误
sub.transfer(tt);
ClassTest<? extends Peple> tt2 = new ClassTest();
//tt2.setFirtsFiled(new Man());错误
//tt2.setFirtsFiled(new Peple());错误
Peple pp =tt2.getSecondFiled();
}
}

当调用 sub.test(tt); 时发生错误,我们不能把一个 ClassTest<Man> 传递给这个方法, tt 的类型是 ClassTest<People> ,但定义 public void transfer(ClassTest<? extends Peple> p ) 使用通配符后 sub.transfer(tt); 可以正确使用。
再看下面的两个错误,使用通配符后set方法和get方法显然为

1
2
void setFirtsFiled(? extends Peple)
? extends Peple getSecondFiled()

编译器只知道要将 People 的子类型,但未具体指定,所有set方法会报错,而get方法就没这个问题,有点类似于多态的子类对象指定父类引用,返回一个 People 子类型没有问题。

通配符的超类限定
extends 来匹配子类,当然也有 super 来指定超类型限定,使用的意思刚好相反

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SubClass {
public void test(ClassTest<Man> m){
}
public void transfer(ClassTest<? super Man> m ){
}
public static void main(String[] args) {
SubClass sub = new SubClass();
ClassTest<Peple> tt = new ClassTest<>();
//sub.test(tt);错误
sub.transfer(tt);
ClassTest<? super Man> tt2 = new ClassTest();
tt2.setFirtsFiled(new Tom());//Tom继承自Man
tt2.setFirtsFiled(new Man());
//tt2.setFirtsFiled(new Peple());错误
//Peple pp = tt2.getSecondFiled();错误
}
}

transfer 方法允许使用通配符方式传进一个 ClassTest<Peple> ,因为 PeopleMan 的超类。下面的两个错误是因为此时不确定get方法返回的对象类型无法保证,只能把它赋给一个 Object ,而set方法可以使用任意 Man 对象或它的子类型调用它。