协变
协变的使用场景:
public static double sum (List<? extends Number> list) {
double result = 0;
for (Number number : list) {
result += number.doubleValue();
}
return result;
}
通过协变 sum 方法可以计算 Number 所有子类的和。 协变放宽了泛型对子类型的约束,那宽松的代价是什么呢?
public static double sum (List<? extends Number> list) {
//下面的代码无法编译
list.add(1);
...
return result;
}
在泛型协变后就无法在列表里添加数据了,当我们在上面代码中加入 list.add(1.0) 这行代码,编译会报错。这个也比较容易理解,当我们传入的是 List<Intger> ,此时我们添加 1.0 肯定是不合理的。总的来说协变可以让泛型的约束更加宽松,但代价是无法调用协变后对象的含泛型参数的方法了。
逆变
逆变使用场景:
public static <T> List<T> removeIf(List<T> list, Filter<? super T> filter) {
List<T> removeList = new ArrayList<>();
for (T t : list) {
if (!filter.predicate(t)) {
removeList.add(t);
}
}
list.removeAll(removeList);
return list;
}
通过逆变我们可以使用父类的 fliter去过滤子类型的List。逆变放宽了泛型对父类型的约束,当然宽松也是有代价的。
List<? super Double> list = new ArrayList<>();
Double number = list.get(0);//无法编译
Object number = list.get(0);//只能获取到顶级类型Object
泛型擦除
ArrayList<String> stringList = new ArrayList<>();
ArrayList<Integer> intList = new ArrayList<>();
System.out.println(stringList.getClass() == intList.getClass());
当我们执行上面一段代码,可以看到不同泛型的两个ArrayList的Class是相同的,看起来好像泛型从我们的字节码抹除掉一样。但是通过反射 getGenericInterfaces方法我们却能拿到父类的泛型信息,那泛型是否真的在我们的字节码擦除了呢?我们看一下通过 javac -g:vars、javap -v命令看一下编译后的字节码。
源Java类:
public class A {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
}
}
class字节码(字节码经过精简)
Constant pool:
#2 = Class #21 // java/util/ArrayList
#3 = Methodref #2.#20 // java/util/ArrayList."<init>":()V
{
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: return
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
8 1 1 list Ljava/util/ArrayList;
LocalVariableTypeTable:
Start Length Slot Name Signature
8 1 1 list Ljava/util/ArrayList<Ljava/lang/String;>;
}
我们看到第十行代码确实是把ArrayList的泛型擦除了,但是却在LocalVariableType里面对我们的泛型进行了记录,也就是说字节码并没有真正的擦除我们的泛型,我们依然可以通过字节码获取到泛型信息。比如Class为我们封装的获取泛型信息的方法 getGenericInterfaces获取父类泛型信息、getReturnType获取方法返回值泛型等方法,其他的位置的泛型我们可以通过字节码工具javasssist等获取。总之泛型擦除并不是真正的在字节码擦除掉泛型信息,而是我们不能通过Class类拿到源代码中设置的泛型限制。
913

被折叠的 条评论
为什么被折叠?



