问题 和在Java中 - 为什么它以这种方式工作?


还有一个新手,试图理解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);
    }
}

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

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

Eclipse说:

“该方法在类型中添加(捕获#3-of?extends Person)   列表不适用于   论据(学生)“

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

不过,以下代码:

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

编译并运作良好(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;
    }

}

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

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

4126
2018-02-27 11:25


起源

假设你有两个班级 Student & Teacher 延伸 Person。现在,你做了一个清单 List<? extends Person> 并假设你添加了 Student 在它,那你怎么会确定 list.get(0) 只会回来 Student 并不是 Teacher?为防止这种歧义,不允许这样做。 - user2004685
读 stackoverflow.com/questions/2723397/java-generics-what-is-pecs - JB Nizet
我希望,list.get(0)将返回一个对象,它将被转换为超类类型,并且我将只能调用在超类(Person)中声明的方法。 - Victor Zhdanov


答案:


我看待它的方式就是这个 - 占位符 T 代表一种确定的类型,在我们需要知道我们需要能够解决的实际类型的地方。相比之下,通配符 ? 意味着任何类型,我永远不需要知道那种类型。你可以使用 extends 和 super 限制以某种方式限制该通配符,但无法获得实际类型。

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

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

在使用有界通配符与“标准”泛型类型参数的情况下,差异的值开始变得明显。假设我有3个班级 PersonStudent 和 Teacher,与 Person 是基地 Student 和 Teacher 延伸。在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> 都可以传递给方法。

现在,假设您有另一种方法想要将学生添加到列表中。如果方法参数采用 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的核心,您可以在其他地方更详细地阅读... 什么是PECS(制作人扩展消费者超级)? http://www.javacodegeeks.com/2011/04/java-generics-quick-tutorial.html


15
2018-02-27 12:13



非常感谢,你的回答非常好!现在我觉得自己更接近于理解Java中的泛型。另外,我错过了这个事实,不像 Person 和 Student 类,之间没有继承关系 Person 和 Student 在 List<Person> 和 List<Student> (据我所知,由于类型擦除,实际上这个列表中的参数类型之间没有关系?)。 - Victor Zhdanov
是的,所以类型擦除意味着“引擎盖下”它们在运行时都只是List。为了保持类型安全性,编译器需要匹配所有泛型类型参数并确保所有内容都兼容。因此,泛型类型参数中的继承关系最终与可以通过纯运行时类型检查验证的内容略有不同。 - sisyphus
我仍然不明白为什么你不能添加,例如,a new Dog() 到了 List<? extends Animal>。编译器不能看到狗类扩展动物吗? - Ogen
因为列表可能是List <Cat>,这也是一个有效的List <?扩展动物>。您不能将Dog添加到List <Cat>,因此编译器不能 知道 如果可以安全地将狗添加到列表<?扩展动物>。 - sisyphus
@San你不能添加任何东西 List<? extends Animal> 因为你不知道列表中对象的实际底层类型是什么 - 所以你不能说服编译器你的方法调用是类型安全的。您 能够 从该列表中获取项目,只要将它们分配给类型的引用即可 Animal。 - sisyphus