zoukankan      html  css  js  c++  java
  • 浅谈Java泛型中的extends和super关键字

      泛型是在Java 1.5中被加入了,这里不讨论泛型的细节问题,这个在Thinking in Java第四版中讲的非常清楚,这里要讲的是super和extends关键字,以及在使用这两个关键字的时候为什么会不同的限制。 
      首先,我们定义两个类,A和B,并且假设B继承自A。

    package com.wms.test;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Generic {  
        public static void main(String[] args){  
              List<? extends A> list1 = new ArrayList<A>(); 
    //          list1.add(new A()); //错误,编译器无法确定List所持有的类型,所以无法安全的向其中添加对象
              A a = list1.get(0);
              List<? extends A> list2 = new ArrayList<B>();  
              List<? super B> list3 = new ArrayList<B>(); 
              list3.add(new B());
              //想要正确,必须向下转型,但是向下转型是不安全的,非常容易出错
    //          B b = list3.get(0); //编译器无法确定get返回的对象类型是B,还是B的父类或 Object.
              List<? super B> list4 = new ArrayList<A>();  
           } 
    }  
    
    class A{}
    class B extends A{}

      从上面这段创建List的代码我们就更加容易理解super和extends关键字的含义了。首先要说明的一点是,Java强制在创建对象的时候必须给类型参数制定具体的类型,不能使用通配符,也就是说new ArrayList<? extends A>(),new ArrayList<?>()这种形式的初始化语句是不允许的。 

      从上面main函数的第一行和第二行,我们可以理解extends的含义,在创建ArrayList的时候,我们可以指定A或者B作为具体的类型。也就是,如果<? extends X>,那么在创建实例的时候,我们就可以用X或者扩展自X的类为泛型参数来作为具体的类型,也可以理解为给"?"号指定具体类型,这就是extends 的含义。 

      同样的,第三行和第四行就说明,如果<? super X>,那么在创建实例的时候,我们可以指定X或者X的任何的超类来作为泛型参数的具体类型。 

      当我们使用List<? extends X>这种形式的时候,调用List的add方法会导致编译失败,因为我们在创建具体实例的时候,可能是使用了X也可能使用了X的子类,而这个信息编译器是没有办法知道的,同时,对于ArrayList<T>来说,只能放一种类型的对象。这就是问题的本质。而对于get方法来说,由于我们是通过X或者X的子类来创建实例的,而用超类来引用子类在Java中是合法的,所以,通过get方法能够拿到一个X类型的引用,当然这个引用可以指向X也可以指向X的任何子类。 

        而当我们使用List<? super X>这种形式的时候,调用List的get方法会失败。因为我们在创建实例的时候,可能用了X也可能是X的某一个超类,那么当调用get的时候,编译器是无法准确知晓的。而调用add方法正好相反,由于我们使用X或者X的超类来创建的实例,那么向这个List中加入X或者X的子类肯定是没有问题的, 因为超类的引用是可以指向子类的。 

        最后还有一点,这两个关键字的出现都是因为Java中的泛型没有协变特性的导致的。

    小结(此处还是没太明白,有待解决)

    extends 可用于的返回类型限定,不能用于参数类型限定。
    super 可用于参数类型限定,不能用于返回类型限定。
    >带有super超类型限定的通配符可以向泛型对象中写入,带有extends子类型限定的通配符可以向泛型对象读取。

    什么是PECS? 

    
    

      PECS指“Producer Extends,Consumer Super”。换句话说,如果参数化类型表示一个生产者,就使用<? extends T>;如果它表示一个消费者,就使用<? super T>,可能你还不明白,不过没关系,接着往下看好了。

    下面是一个简单的Stack的API接口:

    public interface Stack<E>{
        public Stack();
        public void push(E e):
        public E pop();
        public boolean isEmpty();
    }
    假设想增加一个方法,按顺序将一系列元素全部放入Stack中,你可能想到的实现方式如下:
    public void pushAll(Iterable<E> iter) {
        for(E e : iter)
            push(e);
    }
    假设有个Stack<Number>,想要灵活的处理Integer,Long等Number的子类型的集合
    Stack<Number> stack = new Stack<Number>();
    Iterable<Integer> iter = null;
    /*
     * 此时代码编译无法通过,因为对于类型Number和Integer来说,虽然后者是Number的子类,
     * 但是对于任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超类,
     * 因为泛型是不可变的。
     */
    stack.pushAll(iter); //错误

      此时代码编译无法通过,因为对于类型Number和Integer来说,虽然后者是Number的子类,但是对于任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超类,因为泛型是不可变的。

      幸好java提供了一种叫有限通配符的参数化类型,pushAll参数替换为“E的某个子类型的Iterable接口”:

    public void pushAll01(Iterable<? extends E> iter) {
        for(E e : iter)
            push(e);
    }
    此时再这样调用:
    /*
     * 这样就可以正确编译了,这里的<? extends E>就是所谓的 producer-extends。
     * 这里的Iterable就是生产者,要使用<? extends E>。
     * 因为Iterable<? extends E>可以容纳任何E的子类。在执行操作时,可迭代对象的每个元素都可以当作是E来操作。
     */
    stack.pushAll01(iter); //正确

      这样就可以正确编译了,这里的<? extends E>就是所谓的 producer-extends。这里的Iterable就是生产者,要使用<? extends E>。因为Iterable<? extends E>可以容纳任何E的子类。在执行操作时,可迭代对象的每个元素都可以当作是E来操作。

      与之对应的是:假设有一个方法popAll()方法,从Stack集合中弹出每个元素,添加到指定集合中去。

    public void popAll(Collection<E> c) {
        c.add(pop());
    }

      假设有一个Stack<Number>和Collection<Object>对象:

    Stack<Number> stack = new Stack<Number>();
    Collection<Object> c = null;
    /*
    * 该方法要正确,必须c为Collection<Number>,和上面同理
    */
    stack.popAll(c); //错误

      同样上面这段代码也无法通过,解决的办法就是使用Collection<? super E>。这里的objects是消费者,因为是添加元素到objects集合中去。使用Collection<? super E>后,无论objects是什么类型的集合,满足一点的是他是E的超类,所以不管这个参数化类型具体是什么类型都能将E装进objects集合中去。

    public void popAll01(Collection<? super E> c) {
        c.add(pop());
    }
    /*
     * 同样上面这段代码也无法通过,解决的办法就是使用Collection<? super E>。
     * 这里的objects是消费者,因为是添加元素到objects集合中去。
     * 使用Collection<? super E>后,无论objects是什么类型的集合,
     * 满足一点的是他是E的超类,所以不管这个参数化类型具体是什么类型都能将E装进objects集合中去。
     */
    stack.popAll01(c);
    综合代码:
    package com.wms.test;
    
    import java.util.Collection;
    
    public class Generic {  
        public static void main(String[] args) {
    
            Stack<Number> stack = new Stack<Number>();
            Iterable<Integer> iter = null;
            /*
             * 此时代码编译无法通过,因为对于类型Number和Integer来说,虽然后者是Number的子类,
             * 但是对于任意Number集合(如List<Number>)不是Integer集合(如List<Integer>)的超类,
             * 因为泛型是不可变的。
             */
            stack.pushAll(iter); //错误
            /*
             * 这样就可以正确编译了,这里的<? extends E>就是所谓的 producer-extends。
             * 这里的Iterable就是生产者,要使用<? extends E>。
             * 因为Iterable<? extends E>可以容纳任何E的子类。在执行操作时,可迭代对象的每个元素都可以当作是E来操作。
             */
            stack.pushAll01(iter); //正确
            
            Collection<Object> c = null;
            /*
             * 该方法要正确,必须c为Collection<Number>,和上面同理
             */
            stack.popAll(c); //错误
            /*
             * 同样上面这段代码也无法通过,解决的办法就是使用Collection<? super E>。
             * 这里的objects是消费者,因为是添加元素到objects集合中去。
             * 使用Collection<? super E>后,无论objects是什么类型的集合,
             * 满足一点的是他是E的超类,所以不管这个参数化类型具体是什么类型都能将E装进objects集合中去。
             */
            stack.popAll01(c);
        }
    }  
    
    class Stack<E> {
        public Stack(){
            
        }
        
        public void push(E e) {
            
        }
        
        public void pushAll(Iterable<E> iter) {
            for(E e : iter)
                push(e);
        }
        
        public void pushAll01(Iterable<? extends E> iter) {
            for(E e : iter)
                push(e);
        }
        
        public E pop() {
            return null;
        }
        
        public void popAll(Collection<E> c) {
            c.add(pop());
        }
        
        public void popAll01(Collection<? super E> c) {
            c.add(pop());
        }
    }


     
  • 相关阅读:
    redis该怎么用
    cookie和session的比较
    web常见攻击
    请大神指导从大日志文件中统计关键字次数的办法
    apache中 MaxClients 与MaxRequestsPerChild
    如何提高缓存命中率
    CSU-ACM2018暑假集训比赛1
    CodeForces
    CodeForces
    CodeForces
  • 原文地址:https://www.cnblogs.com/wangmingshun/p/5389341.html
Copyright © 2011-2022 走看看