浅谈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());
    }
}

 
时间: 2024-11-05 11:31:55

浅谈Java泛型中的extends和super关键字的相关文章

浅谈Java泛型中的extends和super关键字(转)

泛型是在Java 1.5中被加入了,这里不讨论泛型的细节问题,这个在Thinking in Java第四版中讲的非常清楚,这里要讲的是super和extends关键字,以及在使用这两个关键字的时候为什么会不同的限制.    首先,我们定义两个类,A和B,并且假设B继承自A.下面的代码中,定义了几个静态泛型方法,这几个例子随便写的,并不是特别完善,我们主要考量编译失败的问题: public class Generic{ //方法一 public static <T extends A> void

Java泛型中的extends和super关键字

理解List<? extends T> list, T key, Comparator<? super T> c 这些一般用在方法形参类型上,用于接受泛型对象. 1.List<? extends T> 代表任意T的子类,比如List<? extends Person>表示可以接受任意一个泛型类型是任意Person子类的list对象,这个一般用于方法调用,表示接受的每一个List<? extends Person>中的元素都is a Person,

转载 浅谈C/C++中的static和extern关键字

浅谈C/C++中的static和extern关键字 2011-04-21 16:57 海子 博客园 字号:T | T static是C++中常用的修饰符,它被用来控制变量的存贮方式和可见性.extern "C"是使C++能够调用C写作的库文件的一个手段,如果要对编译器提示使用C的方式来处理函数的话,那么就要使用extern "C"来说明.本文主要介绍C/C++中的static和extern关键字. AD: static是C++中常用的修饰符,它被用来控制变量的存贮方

浅谈Java泛型之&lt;? extends T&gt;和&lt;? super T&gt;的区别

关于Java泛型,这里我不想总结它是什么,这个百度一下一大堆解释,各种java的书籍中也有明确的定义,只要稍微看一下就能很快清楚.从泛型的英文名字Generic type也能看出,Generic普通.一般.通用的,是一个概括性的词,那么泛型从名字上也就好理解了,它是一种通用类型,是java中各种类型的概括. ?是java泛型中的通配符,它代表java中的某一个类,那么<? extends T>就代表类型T的某个子类,<? super T>就代表类型T的某个父类. 这里我们先定义一组

浅谈Java 8中的方法引用(Method References)

本人接触Java 8的时间不长,对Java 8的一些新特性略有所知.Java 8引入了一些新的编程概念,比如经常用到的 lambda表达式.Stream.Optional以及Function等,让人耳目一新.这些功能其实上手并不是很难,根据别人的代码抄过来改一下,并不要知道内部的实现原理,也可以很熟练地用好这些功能.但是当我深究其中一些细节时,会发现有一些知识的盲区.下面我就来谈一下Java 8中的Method References这个概念. 首先我给出官方对于这一概念的详细解释,https:/

Java泛型 通配符? extends与super

本文来源:https://i.cnblogs.com/EditPosts.aspx?opt=1 感谢博主.本文仅供参考学习. Java 泛型 关键字说明 ? 通配符类型 <? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类 <? super T> 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至Object extends 示例 static class Food{} static class Frui

浅谈java类中成员的初始化顺序(一)

类被创建之后的成员的初始化顺序到底是怎么样的? 首先 不考虑继承 package com; public class DemoOne { /** * 关于类的初始化顺序 */ //不考虑继承结构的情况 private static int a=1; private String str="我被赋值了"; static{ //为什么static成员函数不能访问非static变量, 不能调用非static成员函数? //静态代码块独立于对象而存在 不依赖于对象存在 简单来说可以直接以类型名

浅谈C/C++中的static和extern关键字 转

原文:http://developer.51cto.com/art/201104/256820.htm static是C++中常用的修饰符,它被用来控制变量的存贮方式和可见性.extern, "C"是使C++能够调用C写作的库文件的一个手段,如果要对编译器提示使用C的方式来处理函数的话,那么就要使用extern "C"来说明. 一.C语言中的static关键字 在C语言中,static可以用来修饰局部变量,全局变量以及函数.在不同的情况下static的作用不尽相同.

Java泛型中的协变和逆变

Java泛型中的协变和逆变 一般我们看Java泛型好像是不支持协变或逆变的,比如前面提到的List<Object>和List<String>之间是不可变的.但当我们在Java泛型中引入通配符这个概念的时候,Java 其实是支持协变和逆变的. 看下面几行代码: // 不可变 List<Fruit>fruits =newArrayList<Apple>();// 编译不通过 // 协变 List<?extendsFruit>wildcardFruit