泛型

参考资料:
Java 泛型总结(一):基本用法与类型擦除
Java 泛型总结(二):泛型与数组
Java 泛型总结(三):通配符的使用
java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一
JAVA——泛型类和泛型方法(静态方法泛型)

泛型的设计

泛型类

假设我要创建一个泛型类,比如天平,Balance。那我可以采用如下代码定义这个类:

public class Balance<T>{
    private T someThing;
    public void put(T a){
        someThing = a;
    }
    public T get(){
        return someThing;
    }
}

这就是一个简单的泛型类,即通过尖括号来表示Table引入了一个类型T,要注意的是T可以用其他大写符号代替,不过常用T,U,S表示任意类型,E表示集合类型,K,V表示关键字和值。
如果采用了多个变量,则可以写为public class Balance<T, U>用逗号分开。

类型变量T的限定

如果这个天平只想用来称食物(Food类),则我们可以对类型变量T进行限定,限定词采用extends,使用方法:public class Balance <T extends Food>{}
并且限定不仅仅可以用类,也可以用接口(如甜的Sweet接口,酸的Sour接口),类型变量T,U直接用逗号隔开,限定类型用&号。多个限定可以写为<T extends Food & Sweet & Sour, U extends Cloth>但是要注意的是限定接口数量不限,但是限定类只用有一个(因为Java是单继承制)。

泛型方法

泛型方法可以参考Java中的泛型方法
泛型方法可以定义在泛型类中,也可以定义在普通类之中。假如我想定义一个新的泛型方法读数read1,可以在天平类中加入:

public class Balance<T>{
    public int read(T a){
        return 9;
    }
    public <T> int read1(T a){
        return 19;
    }
}

在这里想说的是,read是一个泛型类的普通方法,而read1则为泛型方法,识别方法就是public后int前的<T>,要注意的是这个T和泛型类的T指的不是同一个类,泛型方法的T起到了类似掩盖的作用,比如在Balance<apple>中,可以有read1(new apple()),也可以使用read1(new banana()),但是不能有read(new banana()),因为read的接受参数T由类Balance决定,而read1的接受参数由该方法的声明<T>决定。
另外,对于泛型类中的静态方法只能写成泛型静态方法,而不能使用泛型类中的类型变量。原因参考类型擦除,我的理解是由于编译过后,泛型类的类型变量被擦除了,而静态方法不需要泛型类实例化,所以此时静态方法就没法找到对应的类型变量。

类型擦除

对于Java泛型来说,他是一个伪泛型。即泛型类的泛型变量只是在编译时存在,在运行时候全部会被擦除,比如当泛型类为Banlance<T>时,Balance<apple>``Balance<orange>在运行时全部会变为Balance<Object>,如果T有限定,如Balance<T extends Food>,则会变成Balance<Food>,唯一的区别就是在输出是,Java会自动帮你进行强制转换。

类型擦除导致的后果

桥方法

不仅泛型类会被类型擦除,泛型方法也会。但是考虑到Java的多态特性(不同的类会自动调用对应的同名方法),这很可能会导致不好的后果。比如

public abstract class Food{
    public int getWeight(){
        return 0;
    }
}

public class Apple extends Food{
    public int getWeight(){
        return 3;
    }
}
public class Orange extends Food{
    public int getWeight(){
        return 5;
    }
}
public class Balance<T extends Food>{
    public int read(T a, int number){
        return T.getWeight()*number;
    }
}
public class Test{
    public static void main(String[] args){
        Balance<Apple> aBal = new Balance<>();
        aBal.read(new apple(), 2);
    }
}

在上面这个例子里面,在运行时,Balance的类型被擦除了,那么aBal.read()实际是调用的Food的还是Apple的.read()就会有问题。因此,编译器通过内置的桥方法来解决这一问题(详见P318)。

不支持泛型数组

参考
java中,数组为什么要设计为协变?
Java——协变数组和类型擦除(covariant array & type erasure)
简单来说,就是对一个父类变量,如Number[] num,我们可以赋给其子类数组,如Number[] num = new Integer[10]。这就是因为数组的协变,因为数组在每次存入时都会进行类型检查,所以是安全的。但是对泛型来说,由于在运行时,类型会被擦除,所以类型检查对泛型就无效了,因此就不能运行泛型数组的出现。
解决方案有很多,个人认为比较方便的是采用数组链表类,即ArrayList<Table<T>>的形式。

泛型的使用

泛型类的继承

即使泛型类的类型变量T有继承关系,对应的泛型类之间也是没有的。如Balance<Fruit>Balance<Apple>就没有继承关系。

通配符

参考
Java 之泛型通配符 ? extends T 与 ? super T 解惑
Java 泛型 <? super T> 中 super 怎么 理解?与 extends 有何不同?

通配符“?”的一个作用就是为解决变量的转型问题。比如我们可以设置水果类变量f,并把新建的苹果对象赋给f
Fruit f = new apple();
但是我们不能对泛型类这么做,如
Balance<Fruit> bF = new banlance<Apple>();//报错
为了解决这样的情况(特别是在方法中规定形参时),我们引入了通配符。
值得注意的是,Balance<T extends Fruit>Balance<? extends Fruit>的使用是完全不同的。前者用在对泛型类,泛型方法的设计编写之中,后者用在对已经编写好的泛型类的引用上。

上界限定通配符

形如Balance<? extends Fruit>表示上界限定通配符,即对于这个泛型类,其中所能包含的类型最高就是Fruit类,其余的就是Fruit的子类了。
这就意味着:

  • 无法存入数据
  • 可以读取数据

无法存入数据是因为当我第一次存入数据时,在程序中Java会临时分配一个类型编号给这个类,再次存入数据时,由于与这个临时类型不同,而存入失败,因此上界限定通配符就只能存入null。
可以读取数据,因为该泛型类中最高类为Fruit类,因此只要声明一个Fruit变量,就可以接受该泛型类中的数据(Furit子类的数据会向上转型,并应用多态)。

下界限定通配符

形如Balance<? super Fruit>表示下界限定通配符,即对于这个泛型类,其中所能包含的类型最低为Fruit类,其余的都是Fruit的父类如Object,Food等。
这就意味着:

  • 可以存入Fruit类及子类数据
  • 可以读出Object类的数据

可以存入限定类及其子类数据,是因为给泛型类中,最低为Fruit类,因此只要存入子类都可以向上转型存入而不会产生错误。
只能读出Object类的数据,因为下界限定通配符只能保证下界,上界最高到Object类,因此为了保证转型正确,只能输出Object类。

无限定通配符

形如Balance<?>的称为无限定通配符。无限定通配符资料较少,涉及到的用法详见核心卷一的P334

补充

关于通配符的使用,有时候很方便,有时候有些麻烦。根据核心卷一,提出了一种改善方法,就是采用辅助方法。具体如下:

public static void swap(Pair<?> p){
    swapHelper(p);
}
public static void swapHelper(Pair<T> p){
    T t = p.getFirst();
    p.setFirst(p.getSecond());
    p.setSecond(t);
}

这样就可以避开<?>禁止读写的权限了。

反射与泛型

反射在泛型中主要是应用于新建对象,构造工厂方法。即利用Class<T>泛型类的构造器T newInstance()去产生任意类的工厂方法。详见核心卷一P338.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值