• Java泛型边界问题,super和extends关键字


    背景

    为什么JDK5要引入泛型,泛型保证参数类型一致性。什么叫类型一致?

    假设有继承关系,A <- B <- C <- D <- E,

    ArrayList<C> list;
    list.add(new C());
    list.add(new D());
    list.add(new B());//compile ERROR
    

    并没有破坏list的类型一致性,因为list被声明参数类型时C,最终list中所有引用对象都是按照C的类型取出。

    有了泛型特性之后,Java就应该使用带参数的类型,而不是rawtype。但是为了兼容,仍然支持rawtype使用。
    泛型有些看起来奇怪的特性,在假设不能使用rawtype只能使用泛型的情况下就很好理解了。

    List<Object>List<X>没有层次关系

    Java给定一个具体的类型参数A之后的泛型List<Object>,与给定另一个具体的类型参数X的泛型List<X>之间没有层次关系,不论Object和X类型的层次关系如何。为什么这样设计?

    假设

        ArrayList<String> list1 = get();
        ArrayList<Object> list2 = list1;
        list2.add(new Object());//reasonable
    

    在拿到list2的代码中,由于list2被声明为ArrayList<Object>,因此,可以自然向其中添加Object类型的item,这样,在拿到list1的代码中认为list1的item的类型时一致的,都是String,取出item的时候就可能得到实际为Object的item,这就会导致程序异常。这是很常见的错误发生场景。因此Java禁止他们之间有层次关系。

    缺点:无法实现模板方法,接收多种参数类型的泛型。

        void recvGen(ArrayList<Object> list){};
        
        ArrayList<String> param = get();
        recvGen(param);//ERROR, 两者没有关系,所以不能转型,也不能强制转型
    

    为此,Java引入了一种带有通配符的泛型,<?> <? super A><? extends A>

    extends和super和?

    void recvGen(ArrayList<? extends Object> list){};
        
        ArrayList<String> param = get();
        recvGen(param);//pass
        
    /***********************/
    
     void recvGen(ArrayList<? super String> list){};
        
        ArrayList<String> param = get();
        recvGen(param);//pass    
    
    • super或者extends可以定义一大类的泛型,作为给出具体类型参数的泛型的父类。
    • super或者extends定义的有边界泛型,根据参数类型的层次覆盖判定和具体参数类型泛型之间的层次关系。

    不要望文生义

    super关键字,用于类的方法中表示指向父类对象的引用。
    在泛型边界语法<? super X>中指出泛型下界,不表示可以存入X的父类对象,只能存入X及其子类对象,取出的对象一律按照Object。
    extends在定义类或接口时表示继承父类,在泛型边界语法<? extends X>中指出泛型下界,不表示可以存入具体类的子类对象(实际上不能存入任何指定类型的对象),只表示已经存在其中的所有对象可以按照X类型取出。

    假设有继承关系,A <- B <- C <- D <- E

       void f( List<? super C>  param){...}
    

    List<? super C>List<B>,List<A>,List<C>,List<Object>或者List<? super B>,List<? super A>的父类,对于接受List<? super C>为参数的方法f中,传递上述类型时,可以隐式向上转型,安全。在方法中,取出的item都是Object类型,List<? super C>取出的类型都是Object,不是C或者其他的具体类

    如果需要cast取出的item为具体类型,程序员自己保证存在List中的对象都是可以安全强制转型的。最好的应用场景是不需要针对具体类型的item进行处理,其次是程序员自己保证所有item可以安全强制转型。

    方法f无法接受List<D>List<E>,cast强制转型也无法通过编译,Java不接受没有层次关系的两个类型的强制转型。

    假设方法内

         List<? super C>  param;
         param.add(new B());// ERROR
         param.add(new Object());//ERROR
         param.add(new D());//right
    

    List<? super C>存入item的限制很大而且看起来有点奇怪:任何C的父类和C类型的对象都不能存入,C的子类可以存入。

       PECS(producer extends,Consumer super)规则:extends的泛型是生产者,只能从中读取
       super限制的泛型可以作为消费者,可以从存入对象。——但是这不是super的主要意义。
    
    • 为什么List<? super C> param取出来的是Object,而不是具体类型?

    因为在声明param的时候只要求这个param是List<? super C>,翻译成人话就是声明param是List<B>,List<A>,List<C>,List<Object>或者是List<? super B>,List<? super A>中的一种,不能保证是具体的哪一种,所以编译器不知道取出的item是哪一种具体类型。如果程序员自己能够保证传入的是同一种具体类型的item,那么自行强制转型,但是编译器只能简单地约定从List<? super C>中取出的Object类型。

    • 为什么不能向List<? super C>存入A B Object类型,而可以存入C和D E?

    因为存入A B或者Object类型可能会破坏原来List的类型一致性,正是为了避免这个问题才在JDK5引入泛型特性的,因此不能允许。而存入C的子类时,通过隐式转换为C可以保证不因此破坏List中item类型的一致。

    • 既然不能保证取出的item是同一种具体类型,要super有何用?

    super的意义在于放大模板方法接受的泛型参数类型,同时提供向这个泛型写入的合法性。
    extends的意义也是在于放大模板方法接受的泛型参数类型,牺牲向泛型写入的可能性,提供保证读取出的类型有具体类型的便利性。

    extends

    extends修饰的泛型是只读的,并且保证读出的类型都是具体类型。

        List<? extends C>  void f(){
            List<? extends C> ret = getNewList();
            for(int i =0; i<10; i++){
                ret.add(new C());//ERROR
                ret.add(new D());//ERROR
                ret.add(new Object());//ERROR
                ret.add(null);
            }
            List<D> tmp = getNewList();
            for(int i =0; i<10; i++){
                tmp.add(new C());//ERROR
                tmp.add(new D());//pass
                tmp.add(new E());//pass
                tmp.add(null);//pass
            }
            ret = tmp;
            return ret;
        }
    

    一个extends通配的泛型List<? extends C>,不可以向其中添加任何具体类型,除了null。
    道理和List<Object>不能接受List<String>向上转型一样,add任何具体类型可能破坏泛型原有的类型一致性,禁止。
    有没有使用super比使用extends来限定泛型边界的更好的场景?

    大概就是当上界不需要限制的时候,这时如果使用<? extends Object>,那么就相当于没有限制。
    所以super不是完全可以被extends替代的,多一种选择更好。

    总结

    super和extends的意义更多是在于模板方法的定义中,模板方法希望放大参数类型从而可以接受更多中的泛型参数。extends是只读的,读出的类型具体。super是读写的,但是写入时只能是更具体的类型,读取时类型只能泛化为Object。

  • 相关阅读:
    python locust 性能测试:locust安装和一些参数介绍
    输入一串字符,检查是否可以组成friend
    Django基础
    JQuery基础
    Javascript基础
    CSS基础
    HTML基础
    MYSQL数据库
    I/O模型
    协程-----Coroutine
  • 原文地址:https://www.cnblogs.com/linlei2099/p/8989151.html
Copyright © 2020-2023  润新知