架构基础-泛型

协变

协变的使用场景:

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:varsjavap -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类拿到源代码中设置的泛型限制。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值