class MyHolder<T>{
private T a;
public MyHolder(T a){this.a = a;}
public T get() {
return a;
}
public void set(T a) {
this.a = a;
}
}
class TestMyHolder {
public static void main(String[] args) {
MyHolder<String> s0 = new MyHolder<>("sss");
//MyHolder<String> s1 = new MyHolder<Integer>(111); //编译报错,类型不兼容
System.out.println(s0.get());
MyHolder<Integer> i0 = new MyHolder<>(111);
//MyHolder<Integer> i1 = new MyHolder<String>("sss"); //编译报错,类型不兼容
System.out.println(i0.get());
}
}
泛型即“参数化类型”。
用T表示类型参数,用尖括号括住T,放在类名后面,这样类中的成员(属性和方法)就可以使用T了。当我们new出一个MyHolder实例s0,指定s0持有的类型参数为String,则构造函数、set()能够访问到类上的类型参数T,知道T是String,构造函数、set()就只接受String类型的实参。同理,在s0的say()返回值类型的是String。
创建第二个MyHolder实例i0时,指定类型参数T为Integer,与s0一对比,MyHolder类名上的T就像是一个参数,可以接各种类型,且只接受类型,不接受123,"abc"等这些具体的值(也不接受基本类型)。所以T叫做类型参数。
泛型方法
<T>叫做参数列表,列表中的类型参数可以有多个,如:<A, B>
将泛型参数列表置于返回值前的方法叫做泛型方法。
static方法不能访问类上的类型参数,如果static方法需要使用泛型能力,就必须将static方法定义为泛型方法。
class MyHolderStatic{
//在返回值前加上泛型参数列表<T>,say方法就成为了泛型方法
public static <T> T say(T params) {
System.out.println(params);
return params;
}
}
擦除
class TestWipe {
public static void main(String[] args) {
MyHolder<String> s0 = new MyHolder<>("sss");
MyHolder<Integer> i0 = new MyHolder<>(111);
System.out.println("MyHolder<String> 和 MyHolder<Integer>在运行时,是同一类型吗?");
System.out.println(s0.getClass() == i0.getClass());
System.out.println("s0的类型参数:" + Arrays.toString(s0.getClass().getTypeParameters()));
System.out.println("i0的类型参数:" + Arrays.toString(i0.getClass().getTypeParameters()));
}
}
泛型擦除:在静态代码检查阶段jvm知道类型参数具体是什么类型,如:MyHolder<String>的类型参数是String,MyHolder<Integer>的类型参数是Integer。但是在运行时,jvm会将类型参数删掉,MyHolder<String>、MyHolder<Integer>都被擦除为原生类型MyHolder。通过s0.getClass().getTypeParameters()可以知道,我们的本意是获取s0、i0的类型参数,但是在运行时仅能获取到类型参数的占位符T,而没能获取到String、Integer。
在实际编程中,获取类型参数却仅仅得到占位符,其实就相当于没有获取到任何有用的信息。
java泛型的上界通配符、下界通配符
<? extends T> 是指“上界通配符”
<? super T> 是指“下界通配符”
// 盘子
public class Plate<T> {
private T item;
public Plate(T t){item=t;}
public void set(T t){item=t;}
public T get(){return item;}
}
//Lev 1 食物
public class Food {
}
//Lev 2 水果、肉
class Fruit extends Food{}
class Meat extends Food{}
//Lev 3
class Apple extends Fruit{}
class Banana extends Fruit{}
class Pork extends Meat{}
class Beef extends Meat{}
//Lev 4
class RedApple extends Apple{}
class GreenApple extends Apple{}
上界通配符 <? extends T>
? extends Fruit 表示一个范围,范围是:水果 或 水果的子类 ,范围上限是水果
Plate<? extends Fruit> 表示装 水果 或者 具体的一种水果 的盘子
Plate<? extends Fruit> fruitPlate = new Plate<>(new Apple());
Fruit fruit = fruitPlate.get(); // 编译正常
fruitPlate.set(new Fruit()); // 编译报错
fruitPlate.set(new Banana()); // 编译报错
fruitPlate.set(new Apple()); // 编译报错
fruitPlate.set(null); // 编译正常
Fruit fruit = fruitPlate.get(); // 编译正常
get()方法正常,因为盘子中不管是 Apple、Banana 都是水果,取出来是水果不会报错。
fruitPlate.set(new Fruit()); // 编译报错
fruitPlate.set(new Banana()); // 编译报错
fruitPlate.set(new Apple()); // 编译报错
set()方法报错,因为JAVA编译器要求,盘子中的水果类型必须是具体的一种水果,不能一会儿是Apple、一会儿是Banana。为了满足JAVA编译器的要求,则不能往盘子中装东西,即不能使用fruitPlate.set(水果)。有一种例外情况: fruitPlate.set(null); 是可以的,因为set(null)相当于没装东西。
换成 ArrayList<? extends Fruit> 会更容易理解
ArrayList<? extends Fruit> fruitList = new ArrayList<>();
fruitList.add(new Fruit()); // 编译报错
fruitList.add(new Apple()); // 编译报错
fruitList.add(new Banana()); // 编译报错
fruitList.add(null); // 编译正常
Fruit fruit = fruitList.get(0); // 编译正常
前面讲了 ? extends Fruit 表示一种范围,水果 或者 水果子类具体的一种,范围上限是水果,没有下限。
所以就要在编译阶段限制 fruitList 不能 add 任何东西,不然就会破坏“ fruitList 中的水果必须是具体的一种水果”的原则。
当然 add null 是可以的,add null 相当于没装东西。
下界通配符 <? super T>
? super Fruit 表示一个范围,范围是:水果 或 水果的父类,范围无上限,下限是水果(注意:由于下限粒度只精确到水果,此时Apple、Banana都被编译器认为是水果)
Plate<? super Fruit> 表示装 水果 或者 具体的一种水果父类 的盘子,并且编译器限制盘子中的东西必须是具体的一种,不能一会儿是Fruit、一会儿是Foot。注意:下限(粒度)只精确到水果级别
Plate<? super Fruit> fruitPlate = new Plate<>(new Fruit());
Object obj = fruitPlate.get(); // 编译正常
Fruit fruit = fruitPlate.get(); // 编译报错
fruitPlate.set(new Food()); // 编译报错
fruitPlate.set(new Object()); // 编译报错
fruitPlate.set(new Fruit()); // 编译正常
fruitPlate.set(new Banana()); // 编译正常
fruitPlate.set(null); // 编译正常
Object obj = fruitPlate.get(); // 编译正常
这行代码好理解,“装 水果 或者 具体的一种水果父类 的盘子”取出来的东西必然是Object类型
Fruit fruit = fruitPlate.get(); // 编译报错
“装 水果 或者 具体的一种水果父类 的盘子”取出来的东西不一定是水果
fruitPlate.set(new Food()); // 编译报错
fruitPlate.set(new Object()); // 编译报错
盘子必须要装 水果 或者 具体的一种水果父类 的盘子
fruitPlate.set(new Fruit()); // 编译正常
fruitPlate.set(new Banana()); // 编译正常
fruitPlate.set(null); // 编译正常
由于下限粒度只精确到水果,并且要求是“水果 或者 具体的一种水果父类 ”,而不是“具体的一种水果”,Fruit、Banana 都属于水果,满足“水果 或者 具体的一种水果父类 ”这个条件,所以编译通过
换成 ArrayList<? super Fruit> 会更容易理解
ArrayList<? super Fruit> superList = new ArrayList<>();
superList.add(new Fruit()); // 编译正常
superList.add(new Apple()); // 编译正常
superList.add(new Banana()); // 编译正常
superList.add(null); // 编译正常
superList.add(new Food()); // 编译报错。
superList.add(new Object()); // 编译报错
Object object = superList.get(0); // 编译正常
Fruit f = superList.get(0); // 编译报错
? super Fruit 表示一种范围: 水果 或者 具体的一种水果父类 ,下限粒度只精确到水果,没有上限。
superList.add(new Fruit()); // 编译正常
superList.add(new Apple()); // 编译正常
superList.add(new Banana()); // 编译正常
这3行代码能编译通过是因为:下限的粒度只精确到水果,Apple、Banana都是水果
superList.add(new Food()); // 编译报错。
superList.add(new Object()); // 编译报错
这2行代码编译报错是因为不满足“superList必须是水果 或者 具体的一种水果父类”,这就导致superList不能add水果的父类
Object object = superList.get(0); // 编译正常
从“水果 或者 具体的一种水果父类”列表中取出来的东西,都属于Object。