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

2022-09-01 07:39:05

又一个新手,试图理解Java泛型。我发现,我已经观察了所有主题,但我仍然有很大的问题。你能不能给我解释一下以下几点:

  1. <? extends SomeClass>意味着,即“任何类型”,并且意味着,此任何类型只能是 的子类。好吧,我写了两个初级课程:?extends SomeClassSomeClass
abstract class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
}

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

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

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

日食 说:

“方法 add(capture#3-of ?扩展人员)类型列表中不适用于参数(学生)”

当我们声明 List、由 、参数化并精确扩展类时,class 怎么可能不适用?Student<? extends Person>StudentPerson

但是,以下代码:

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

编译并运行良好(传递给println方法,向我展示了调用的正确结果)。据我所知,这意味着我可以将任何类型传递给此列表,即我们类的超类型(在我们的例子中,它只是类)。但是我们看到,与逻辑相反,我们可以很容易地将子类学生添加到我们的!list.get(0)toStringList<? super Person>PersonObjectList<? 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”)从这个更新的类(例如,我们的对象“clarissa”)实例化)传递给 。这样做,我们的意思是,我们可以将子类存储在其超类的集合中。也许,我不理解一些基础的想法,但在这个阶段,我看不出将子类添加到其超类的集合与将对象“clarissa”的引用分配给可变的,类型化的Person之间有任何区别。当我们想要使用超类变量处理其中一个时,我们对可调用方法有相同的简化。那么,为什么不以相同的方式工作,其中相反地工作呢?List<? extends Person>List<? extends SomeClass>List<? super SomeClass>

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

答案 1

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

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

当您有 .在这里,我们说集合是确定的类型,它是 的超类型。这意味着您始终可以向其添加对象。由于您不知道实际类型,因此您无法做的是从中检索对象。所以你现在有了一种只写集合。List<? super MySuper>MySuperMySuper

使用有界通配符与“标准”泛型类型参数的地方是差异值开始变得明显的地方。假设我有 3 个类 ,和 ,作为基础和扩展。在 API 中,您可以编写一个方法,该方法获取集合,并对集合中的每个项执行某些操作。这很好,但你真的只关心集合是某种与接口兼容的类型 - 它应该与界面一起工作并且同样好。如果像这样定义方法PersonStudentTeacherPersonStudentTeacherPersonPersonList<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();
    }
}

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

现在,假设您有另一种方法想要将学生添加到列表中。如果方法参数采用 a,则即使应该没问题,它也不能采用 a。因此,您将其实现为例如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


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

你之所以能做上面的事情,假设有一个Person类,学生类扩展Person。您可以认为List意味着在此列表中,所有元素都属于Person类或Person的超类,因此当您添加Person类实例或Person类的子类时,将发生隐式转换。例如,个人 = 新生();

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 类实例添加到列表中,则需要显式强制转换或向下转换,编译器不知道说(人员)对象是否可以成功。


推荐