[[扩展类>和超级类>-Java为什么这样工作?

问题描述 投票:23回答:2

另一个新手,试图理解Java泛型。我发现我已经观察了所有主题,但是仍然有很多问题。您能否解释一下以下内容:

  1. <? extends SomeClass>表示?是“任何类型”,而extends SomeClass表示此Any类型只能是SomeClass的子类。好吧,我写了两个基本类:
abstract class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
}

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

Class Student将在我们的示例中为?? extends Person,准确地说。然后,我尝试将新学生添加到ArrayList中,据我从上面的书面了解,该方法适用于所有类,它们是Person的子类:

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

Eclipse说:

“类型的方法add(capture#3-of方法扩展Person)清单不适用于论点(学生)“

Student类不适用,当我们声明List时,<? extends Person>被Paramethrized,而Student恰好扩展了Person类吗?

但是,以下代码:

List<? super Person> list = new ArrayList<>();
list.add(clarissa); 

编译并运行良好(传递给println方法的list.get(0)向我展示了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;
    }

}

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

  1. 我不了解<T>(或<E>或JLS适当部分的任何其他字母)与<?>之间的根本区别。 <T><?>都是typeholders,所以为什么我们有两个“关键词”(这个符号不是关键词,我只是用这个词来强调Java语言中两个符号的重含义)目的?
java generics
2个回答
31
投票

我的观察方式是-占位符T代表一个确定的类型,在需要知道实际类型的地方,我们需要能够解决这个问题。相反,通配符?表示任何类型,而我将永远不需要知道该类型是什么。您可以使用extendssuper界限以某种方式限制该通配符,但无法获得实际的类型。

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

当您有List<? super MySuper>时,逻辑将以其他方式起作用。在这里,我们说的是集合的确定类型,它是MySuper的超类型。这意味着您可以始终向其添加MySuper对象。因为您不知道实际类型,所以无法执行的操作是从中检索对象。因此,您现在有了一种只写集合。

使用有界通配符和“标准”通用类型参数的地方,差异的值开始变得明显。假设我有3个类PersonStudentTeacher,其中PersonStudentTeacher扩展的基础。在API中,您可以编写一个方法,该方法采用Person的集合并对集合中的每个项目进行处理。很好,但是您实际上只关心集合是与Person接口兼容的某种类型的-它应该与List<Student>List<Teacher>均能很好地兼容。如果您定义这样的方法

public void myMethod(List<Person> people) {
    for (Person p: people) {
        p.doThing();
    }
}

那么就不需要List<Student>List<Teacher>。因此,您可以将其定义为采用List<? extends Person> ...

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

您可以这样做,因为myMethod不需要添加到列表中。现在,您发现List<Student>List<Teacher>都可以传递到方法中。

现在,假设您还有另一种方法希望将学生添加到列表中。如果method参数采用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);
    }
}

这是PECS的核心,您可以在其他地方阅读更多详细信息...What is PECS (Producer Extends Consumer Super)?http://www.javacodegeeks.com/2011/04/java-generics-quick-tutorial.html


2
投票
List<? super Person> list = new ArrayList<>();
list.add(clarissa); // clarissa is an instance of Student class

您可以做上述事情的原因,假设有一个Person类,Student类扩展了Person。您可以认为List意味着在此List中所有元素都是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
}

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

© www.soinside.com 2019 - 2024. All rights reserved.