JAVA泛型的价值与本质:为了实现代码更好的复用。
JDK1.5时期,通常使用Object作为通用参数类型来实现复用,但是出现个非常危险的运行时类型问题,泛型是实现了类型的参数化,解决了JDK1.5时期类型转换的不安全问题(ClassCastException)。泛型实现编译时检查而不是运行时抛出异常。
JAVA的泛型是伪泛型,实现上Java泛型通过类型擦除实现:
java泛型擦除的初衷是希望让泛型化程序的调用方可以依赖非泛型的库,反之亦然。(移植性兼容)类型擦除实现了移植兼容性,但是从本质初衷触发的“安全代码复用”,也带来也一些能力限制和安全问题:如支持原生态类型,不支持实例化泛型数组。
普通泛型参数:Result<T> 直接擦除为 Result<Object>
有限的通配符:Result<T extens Number> 擦除为 Result<Number>
理解这个实现就能理解很多Java泛型使用局限的原因。
Effective 中关于 泛型的最佳实践:
最佳实践1:禁止使用原生态类型
List<E> 对应的原生态类型是List,在java中是可以直接使用List的,但是会导致编译时的异常。
最佳实践2:列表优于数组
数组是协变的儿泛型是可变的。
(协变:如果A是B的子类型,那么数组A[]是数组B[]的子类型)
对于任意两个类型A,B,List<A> 既不是list<B>的子类也不是它的父类。
数组是具体化的,泛型是类型擦除的。
数组在运行时,知道和强化他们的元素类型。泛型只在编译时IDE强化它的类型,但是在运行时丢弃它的类型。
最佳实践3:谨慎混用泛型和可变参数
直接实例化泛型数组是非法的:
List<String> [] stringLists = new List<String>[1];// 直接实例化泛型数组,预发报错
可变参数中使用泛型数组却是合法的:
void doSomething(List<String> ... stringLists) //最为可变参数是合法的。
为什么会出现上面的矛盾?带有泛型的可变参数或者参数或类型的方法在实践中用处很大,Java的设计者选择容忍这一矛盾的存在。
如Java类库中的Arrays.asList(T ... a) ;Collections.addAll(Collection(? super T) c, T ... elements) ; EnumeSet.of(E first, E ... rest)
如何规避线上报错?
不能修改可变参数数组中的任何值,可变数据只用来动作消费者使用,而不是生产者。
不降可变参数数组赋值给任何父类数组。
最佳实践4:利用有限制通配符提升API的灵活性
为了最大提升API的灵活性,要在表示生产者和消费者的输入参数上使用有限制通配符。
上界:<? extends Super> 下界:<? super Sub>
以上代码的编译并不能通过,原因是Java编译器拒绝向指定下界通配符定义的集合里进行add操作。
PECS,producer extends consumer super:
使用extends关键子定义的泛型集合只可以不可以写。
使用super可以用来写,不可以用来读。
static class Fruit{
@Override
public String toString() {
return super.toString();
}
}
static class Apple extends Fruit{
@Override
public String toString() {
return super.toString();
}
}
static class Orange extends Fruit{
@Override
public String toString() {
return super.toString();
}
}
// 泛型接口 本质上是定义了个规约模板
static interface FruitPlate<T> {
public T getFruit();
}
static class FruitDish<T> implements FruitPlate<T> {
List<T> fruit;
@Override
public T getFruit() {
return fruit.get(0);
}
}
// 泛型类
static class FruitHolder<T extends Fruit>{
T fruit;
// 泛型方法
FruitHolder(T fruit) {
this.fruit = fruit;
}
public String name() {
return fruit.toString();
}
}
public static Fruit get(List<? extends Fruit> fruits) {
// 编译不通过
//fruits.add(new Orange());
Fruit fruit = fruits.get(0);
System.out.println(fruit);
return fruit;
}
// ?通配符 当赋值的类型不确定的时候,我们用通配符(?)代替了
public static void set(Collection<? super Apple> apples, Apple apple1, Apple apple2) {
apples.add(apple1);
apples.add(apple2);
// 编译不通过 编译器不能确定 apples里的类型
//Apple apple = apples.get(0);
}
public static <T> boolean addAll(Collection<? super T> c, T... elements) {
boolean result = false;
for (T element : elements) {
result |= c.add(element);
}
return result;
}
以上,如有不清晰欢迎留言提问。
如有收获,欢迎关注,点赞,转发。