Java 泛型学习随笔之通配符

402 阅读3分钟

1.java泛型实现方法是擦除法,虚拟机对泛型类型会直接视为Object类型,由编译器对泛型进行安全的强制类型转换

2.协变 由于java的类型擦除机制,会导致类型安全问题,所以java的泛型是不协变的(子类的泛型类型并不是泛型类型的子类),即不能将子类泛型类型对象赋值给父类泛型引用,比如

List<TextView> list = new ArrayList<Button>();

编译器直接报错,这是不允许的,但现在我有个需求,获取List<TextView>中所有view的text并打印,代码如下

private void printText(List<TextView> list) {
        for (TextView textView : list) {
            System.out.println(textView.getText());
        }
}

此时我可以传入List<TextView> list,不可以传List<Button> list,但我们知道ButtonTextView子类,获取getText()是正常逻辑,那么怎么解决这个问题?使用<? extend>上界通配符,将代码修改为

private void printText(List<? extends TextView> list) {
        for (TextView textView : list) {
            System.out.println(textView.getText());
        }
}

就可以传入List<Button> list了。但现在我的需求变了,我想修改这个list,往里面添加一个TextViewlist.add(new TextView(this));,编译器又直接报错了,此操作是不可以的,为什么?因为编译器不知道你会传一个什么泛型集合进来,刚才我们才传了一个List<Button> list进来,这个buttonList自然是不可以接收textView的,所以编译器为了类型安全直接报错。

所以<? extend>上界通配符在满足某些协变需求后,也有限制条件,即可读不可写,你可以获取它的值但不可以修改它,除了添加null

3.逆变 即反协变,java依然不能将父类泛型类型对象赋值给子类泛型引用,比如

List<Button> list = new ArrayList<TextView>();

编译器报错,我现在有一个方法如下

private void addText(List<Button> list) {
        list.add(new Button(this));
}

此时我可以传入一个List<Button> list,但是不可以传入List<TextView> list,因为java依然不能将父类泛型类型对象赋值给子类泛型引用,但如果我只是想保存一下button,传入List<TextView> list自然是合理的,那么如何解决?java提供了<? super>下界通配符,此时代码修改为

private void addText(List<? super Button> list) {
        list.add(new Button(this));
}

就可以传入List<TextView> list乃至List<View> list都是可以的。但是问题来了,我希望在这个方法里面读取list的值呢,答案是不可以的,为什么,还是类型安全问题,因为编译器不知道你传入的List<TextView> list乃至List<View> list集合里面是否保存了textViewview,获取时不能知道正确类型

所以<? super>下界通配符在满足某些逆变需求后,限制条件为可写不可读,即你可以修改它但不可以获取它的值

总结

由于java通过擦除法实现泛型,导致的类型安全问题,使得java的泛型是不协变的,即子类泛型不是父类泛型的子类,比如List<Button>不是List<TextView>的子类。因此java使用通配符来解决不协变问题,但还是因为类型安全问题,使用通配符依然是有限制的,即<? extend>上界通配符可读不可写,<? super>下界通配符可写不可读。只有理解这些因果关系才能真正理解通配符