泛型 一 泛型方法、擦除、边界

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。

Java 泛型 <? super T> 中 super 怎么 理解?与 extends 有何不同? - 知乎

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值