• Java泛型中的 “?super T“ 与 “?extends T“ 有何不同


    经常发现有ListSet的声明,是什么意思呢?表示包括T在内的任何T的父类,表示包括T在内的任何T的子类,下面我们详细分析一下两种通配符具体的区别.

    PECS

    首先,Java的泛型在编译期会做类型擦除,这样会引起安全问题.为了安全,所有与泛型相关的异常都应该在编译期间发现,因此为了泛型的绝对安全,java在设计时做了相关的限制:

    List表示该list集合中存放的都是T的子类型(包括T自身),由于T的子类型可能有很多,但是我们存放元素时实际上只能存放其中的一种子类型(这是为了泛型安全,因为其会在编译期间生成 桥接方法(Bridge Methods),该方法中会出现强制转换,若出现多种子类型,则会强制转换失败),例子如下:

    List<? extends Number> list=new ArrayList<Number>();
    
    list.add(4.0);//编译错误
    list.add(3);//编译错误
    
    • 1
    • 2
    • 3
    • 4

    上例中添加的元素类型不止一种,这样编译器生成的桥接方法中的强制转换会失败,为了安全,Java只能将其设计成不能添加元素。

    虽然List不能添加元素,但是由于其中的元素都有一个共性–有共同的父类,因此我们在获取元素时可以将他们统一强制转换为T类型,我们称之为get原则。

    对于*List*其list中存放的都是T的父类型元素(包括T),我们在向其添加元素时,只能向其添加T的子类型元素(包括T类型),这样在编译期间将其强制转换为T类型时是类型安全的,因此可以添加元素,例子如下:

    List<? super Number> list=new ArrayList<Number>();
    
    list.add(2.0);
    list.add(3.0);
    
    • 1
    • 2
    • 3
    • 4

    但是,由于该集合中的元素都是T的父类型(包括T),其中的元素类型众多,在获取元素时我们无法判断是哪一种类型,故设计成不能获取元素,我们称之为put原则。

    实际上,我们采用extendssuper来扩展泛型的目的是为了弥补例如List只能存放一种特定类型数据的不足.

    请 记住PECS原则:生产者(Producer)使用extends,消费者(Consumer)使用super。

    • 生产者(出) 使用extends

    如 果你需要一个列表提供T类型的元素(即你想从列表中get出T类型的元素),你需要把这个列表声明成,比如List,因此你不能往该列表中添加任何元素。

    • 消费者(入) 使用super

    如 果需要一个列表使用T类型的元素(即你想把T类型的元素put到列表中),你需要把这个列表声明成,比如List,因此你不能保证从中读取到的元素的类型。

    • 即 是生产者,也是消费者

    如 果一个列表即要生产,又要消费,你不能使用泛型通配符声明列表,比如List

    参考java.util.Collections里的copy方法:

    public static <T> void copy(List<? super T> dest, List<? extends T> src)
    
    • 1

    要从src里get所以是extends,要向dest里写入所以是super.

    桥接方法研究

    一个使用Consumer 的例子:

    package wjw.test.java8.generic;
    
    import java.util.function.Consumer;
    
    public class TestGeneric {
      public static void main(String[] args) {
        Consumer<String> c = new Consumer<String>() {   // <1>
          @Override
          public void accept(String s) {
            System.out.println(s);
          }
        };
        c.accept("hello lambda");
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    <1>: 创建了一个具体类型是String的Consumer

    编译生成的字节码:

    Compiled from "TestGeneric.java"
    public class wjw.test.java8.generic.TestGeneric {
      public wjw.test.java8.generic.TestGeneric();
        Code:
           0: aload_0
           1: invokespecial #8                  // Method java/lang/Object."":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #16                 // class wjw/test/java8/generic/TestGeneric$1
           3: dup
           4: invokespecial #18                 // Method wjw/test/java8/generic/TestGeneric$1."":()V
           7: astore_1
           8: aload_1
           9: ldc           #19                 // String hello lambda
          11: invokeinterface #21,  2           // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V
          16: return
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    由于编译时进行了类型擦除,第17行的Consumer.accept的方法里传的参数是Object.

    生成的匿名类:

    import java.util.function.*;
    
    class LambdaOld$1 implements Consumer<String> {
        LambdaOld$1() {
            super();
        }
        
        @Override
        public void accept(final String s) {
            System.out.println(s);
        }
        
        @Override
        public /* bridge */ void accept(final Object o) {
            this.accept((String)o);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在桥接方法accept里,第15行会进行强制类型转换然后调用第9行的接受具体类型参数的accept方法.

  • 相关阅读:
    Vue——axios的二次封装
    Java之网络编程
    nginx
    Mac下使用nvm,执行微信小程序自定义处理命令失败
    力扣练习——9 从链表中删去总和值为零的连续节点
    wireshark分析tcp协议(一)三次握手【理论 + 实操】
    (01)ORB-SLAM2源码无死角解析-(44) EPnP 源代码分析(5)→ICP 求解相机位姿
    AI发展目前最大挑战是什么?
    React整理总结(三)
    Packet Tracer - 配置多区域 OSPFv3
  • 原文地址:https://blog.csdn.net/wjw465150/article/details/126619145