<?扩展Class>和<?Java中的超类>为什么它以这种方式工作?

Vic*_*nov 20 java generics

还有一个新手,试图理解Java Generics.我发现,我观察了所有主题,但我仍然有很多问题.能否请你解释一下以下事项:

  1. <? extends SomeClass>意思?是"任何类型",并且extends SomeClass意味着任何类型都只能是它的子类SomeClass.好的,我写了两个小学课:
abstract class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
}

class Student extends Person {
    public Student(String name) {
        super(name);
    }
}
Run Code Online (Sandbox Code Playgroud)

Student?在我们的示例中.? extends Person确切地说.然后我试图将新学生添加到ArrayList,正如我从上面所写的那样,它应用所有类,它们是Person的子类:

Student clarissa = new Student("Clarissa Starling");
List<? extends Person> list = new ArrayList<>();
list.add(clarissa); //Does not compile
Run Code Online (Sandbox Code Playgroud)

Eclipse说:

"类型List中的方法add(捕获#3-of?extends Person)不适用于参数(Student)"

Student当我们声明List <? extends Person>,参数化并Student完全扩展类时,类怎么可能不适用Person

不过,以下代码:

List<? super Person> list = new ArrayList<>();
list.add(clarissa); 
Run Code Online (Sandbox Code Playgroud)

编译并运行良好(list.get(0)传递给println方法,向我显示toString调用的正确结果).据我所知,List<? super Person>意味着,我可以传递给这个列表任何类型,这是我们Person类的超类型(在我们的例子中它只是Object类).但是我们看到,与逻辑相反,我们可以轻松地将子类Student添加到我们的List<? super Person>!

好吧,抛开我们的情绪,让我们看看,Clarissa Starling在我们的系列中会发生什么.让我们上课Student,并添加几个方法:

class Student extends Person {
    private int grant;
    public Student(String name) {
        super(name);
    }

    public void setGrant(int grant) {
        this.grant = grant;
    }

    public int getGrant() {
        return this.grant;
    }

}
Run Code Online (Sandbox Code Playgroud)

然后我们传递一个对象,从这个更新的类(例如我们的对象"clarissa")实例化到List<? extends Person>.这样做,我们的意思是,我们可以将子类存储在其超类的集合中.也许,我不了解一些基本的想法,但在这个阶段我没有看到将子类添加到其超类集合和将对象"clarissa"的引用分配给变量,类型Person之间的任何区别.当我们想要使用我们的超类变量来处理其中一个方法时,我们可以减少可调用的方法.那么,为什么List<? extends SomeClass>不能以相同的方式List<? super SomeClass>工作,反之亦然?

  1. 我不明白<T>(或<E>,或来自JLS的适当部分的任何其他信件)与之间的根本区别<?>.这两个<T><?>typeholders,那么为什么我们有两个"关键词"(这个符号是不是关键字,我只是用这个词强调Java语言中的两个符号的重意思)为了同样的目的?

sis*_*hus 26

我看待它的方式是 - 占位符T代表一种确定的类型,在我们需要知道我们需要能够解决的实际类型的地方.相比之下,通配符?意味着任何类型,我永远不需要知道该类型是什么.您可以使用extendssuper边界以某种方式限制该通配符,但是无法获得实际类型.

所以,如果我有一个List<? extends MySuper>我所知道的就是它中的每个对象都实现了MySuper接口,并且该列表中的所有对象都是相同的类型.我不知道那种类型是什么,只是它是某种子类型MySuper.这意味着只要我只需要使用MySuper界面,我就可以从该列表中获取对象.我不能做的是将对象放入列表中,因为我不知道类型是什么 - 编译器不会允许它,因为即使我碰巧有一个正确类型的对象,它也不能确保在编译时.因此,从某种意义上说,该集合是一个只读集合.

当你拥有时,逻辑就会以另一种方式运作List<? super MySuper>.在这里,我们说这个集合是一个明确的类型,它是一个超类型MySuper.这意味着您始终可以向其添加MySuper对象.你不能做的,因为你不知道实际的类型,是从它检索对象.所以你现在有了一种只写集合.

在使用有界通配符与"标准"泛型类型参数的情况下,差异的值开始变得明显.比方说,我有3个班Person,StudentTeacherPerson作为基础,StudentTeacher扩展.在API中,您可以编写一个方法,该方法收集集合中的Person每个项目并对其执行某些操作.这很好,但你真的只关心收集一些类型,它是与兼容的Person接口-它应该工作List<Student>以及List<Teacher>同样出色.如果您定义这样的方法

public void myMethod(List<Person> people) {
    for (Person p: people) {
        p.doThing();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后它不能采取List<Student>List<Teacher>.所以,相反,你会定义它采取List<? extends Person>...

public void myMethod(List<? extends Person> people){
    for (Person p: people) {
        p.doThing();
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以这样做,因为myMethod永远不需要添加到列表中.现在你发现了,List<Student>并且List<Teacher>都可以传递给方法.

现在,假设您有另一种方法想要将学生添加到列表中.如果方法参数需要一个,List<Student>那么List<People>即使它应该没问题也不能.所以,你实现它作为一个List<? super Student> 例如

public void listPopulatingMethod(List<? extends Student> source, List<? super Student> sink) {
    for (Student s: source) {
        sink.add(s);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是PECS的核心,你可以在其他地方更详细地阅读...... 什么是PECS(Producer扩展消费者超级)? http://www.javacodegeeks.com/2011/04/java-generics-quick-tutorial.html

  • 因为列表可能是 List&lt;Cat&gt;,它也是一个有效的 List&lt;? 扩展动物&gt;。您不能将 Dog 添加到 List&lt;Cat&gt; 中,因此编译器无法*知道*将 Dog 添加到 List&lt;? 扩展动物&gt;。 (2认同)
  • @San 你不能向 `List&lt; 添加任何内容?extends Animal&gt;` 因为你不知道列表中对象的实际底层类型是什么 - 所以你无法让编译器相信你的方法调用是类型安全的。只要将它们分配给“Animal”类型的引用,您*就可以*从该列表中获取项目。 (2认同)

Dra*_*ake 5

List<? super Person> list = new ArrayList<>();
list.add(clarissa); // clarissa is an instance of Student class
Run Code Online (Sandbox Code Playgroud)

之所以可以做上面的事情,假设有一个Person类,Student类扩展了Person。你可以认为 List 意味着这个 List 中的所有元素都是 Person 类或者 Person 的超类,所以当你添加 Person 类实例或者 Person 类的子类时,就会发生隐式转换。例如 Person person = new Student();

public static void main(String[] args) {
        ArrayList<? super Person> people = new ArrayList<>();
        people.add(new Person());
        people.add(new Object()); // this will not compile
}
Run Code Online (Sandbox Code Playgroud)

但是如果将Object类实例添加到列表中,则需要显式转换或向下转换,并且编译器不知道说(Person)Object是否可以成功。