advanced java (二) 泛型

advanced java 泛型

泛型,即“参数化类型”。泛型一方面来说,可以提高安全性,可以在编译期提前发现错误,另一方面,定义类型后,可以提前确认类型,避免强转问题。

一个强大的类型系统,可以给程序带来鲁棒性和可维护性。

一般来说,泛型有四种使用方法,即除了类型限定,还有类型变异(分为向上和向下)和通配符。因为java里面是伪泛型的实现,泛型只存在于编译期,在运行时,需要做一些处理。在 jvm 中,存在 类型擦除 的问题,在类型擦除的时候,通过 桥方法 ,达到一些 work around 。泛型在与java其他特性在一起时候,会出现一些问题,需要有处理方式。

泛型

类型 代码 表现形式
不变(invariant) <T> 类型限定
协变(covariant) <T extends U > 类型变异,T是U的子类
逆变(contravariant) <T super U > 类型变异,T是U的父类
量化 (quantification) <?> 通配符

前三种都是变性 (Variance),表达了类层次结构和多态类型之间的关系。

不变
interface Delicious{}
interface GoodLooking{}
class Fruit {}
class Apple extends Fruit implements Delicious,GoodLooking {}
class Durian extends Fruit implements Delicious {}

大多数情况下,子类型可以隐性的转换为父类型

ArrayList<Fruit> Fruits = new ArrayList<>();
Fruits.add(new Fruit());
Fruits.add(new Apple());
协变和逆变

协变可以限定并传入父类。而逆变的时候,需要做一些处理,即需要PECS法则(Producer extends Comsumer super)。

public static <T extends Fruit> void doSomethingA(Optional<T> some){
    some.get();
}

public static <OT extends Optional<? super Apple>> void doSomethingB(OT some){
    some.get();
}
doSomethingA(Optional.of(new Apple()));
doSomethingB(Optional.of(new Fruit()));

协变和逆变可以是自己,即<T extends Fruit> ,T可以是Fruit

量化

即可以传入所有类型。

public static void doSomethingC(Optional<?> some){
    some.get();
}
doSomethingC(Optional.of(new Apple()));
doSomethingC(Optional.of(1.0f));

<?><Object> 的区别在于其便利性,List<?>不能添加任何元素。

ArrayList<Object> listA = new ArrayList<>();
listA.add(new Apple());
ArrayList<?> listB = new ArrayList<>();
listB.add(new Apple()); //非法,编译错误

另外,使用一个通配符来捕获类型或许是更好的选择。

public static <T> void doSomethingC(Optional<T> some){
    some.get();
}
& 符号
public static <T extends Fruit & Delicious & GoodLooking> void doSomethingD(Optional<T> some){
    some.get();
}
doSomethingD(Optional.of(new Apple()));
doSomethingD(Optional.of(new Durian())); //非法,编译错误

extends 可以通过 & 符号表示多继承关系。但是继承类必须放在第一个位置。其中Durian没有 GoodLooking接口,会无法通过编译。泛型extends 可以只继承接口,如<T extends Delicious>也是可行的。

类型擦除

在java5 加入泛型之后,为了向前兼容性,采取了类型擦除。java的泛型只在源码中存在,编译后的字节码会替换成原生类型(Raw Type)。

ArrayList<Apple> apples = new ArrayList<>();
ArrayList<Fruit> fruits = new ArrayList<>();
System.out.println(apples.getClass()==fruits.getClass()); //true
apples.add(new Apple());
System.out.println(apples.get(0));

类型擦除后

ArrayList apples = new ArrayList<>();
ArrayList fruits = new ArrayList<>();
System.out.println(apples.getClass()==fruits.getClass());
apples.add(new Apple());
System.out.println((Apple) apples.get(0));

ArrayList<Apple> ArrayList<Fruit> 都变成了原生类型 ArrayList<E>

桥方法

桥方法是编译器在泛型类型和协变返回类型中做的一个 trick ,用来消除类型擦除带来的影响。

class StringList<String> extends ArrayList<String> {
    public boolean add(String e) { return false;}
}

ArrayList<String> strings = new StringList();

这里就会存在泛型方法和多态冲突的问题了,由于父类 ArrayList<T>,类型擦除后,会生成 boolean add(Object e) ,而子类是 boolean add(String e) ,无法继承。

解决方法是,java字节码会偷偷加上一个方法

public boolean add(Object e) {
    add((String)e);
    return false;
}

但是还会有一个问题

class StringList<String> extends ArrayList<String> {
    public String get(int index) { return (String) "";}
}

其父类的实现是E get(int index),类型擦除后,就是 Object get(int index) ,这样返回类型父类是Object,子类是String ,就又出现冲突了。

那在字节码里面这样实现就可以了。以下两个方法的类型签名是相同的,都是get(int),在代码里面是无法实现的,但是在jvm字节码里可以存在。

public String get(int index) {...}
public Object get(int index) {return get(index);}
实例化泛型变量

泛型无法实例化对象

public static <T> void create(T some){
    T o = new T(); //非法,编译错误
    T[] arr = new T[10]; //非法,编译错误
}

或许可以使用反射机制来实现,或者像ArrayList的内部实现,使用Object[]

T o = (T) new Object();
Object[] arr = new Object[10];
T frist = (T) arr[0];
数组

在有泛型时,初始化数组需要一些技巧

@SuppressWarnings("unchecked")
Optional<T>[] optionals = (Optional<T>[]) new Optional<?>[100];
异常

Throwable 的子类不能是泛型的。

class SomeException extends RuntimeException {}

public static <T extends RuntimeException> void throwSomeException(T ex) throws T {
    T o = (T) new RuntimeException();
    throw o;
}

throwSomeException(new SomeException()); //这里会抛出RuntimeException而非SomeException

catch(T ex)是非法的。

public static <T extends Exception> void throwSomeException(T ex) {
    T o = (T) new RuntimeException();
    try {
        throw o;
    } catch (T ex) { //非法,编译错误
        ex.printStackTrace();
    }
}