为什么 Java 在泛型中有下限?

Why does Java have lower bounds in generics?

我正在尝试设计自己的编程语言,并且正在考虑泛型。我已经做 Java 有一段时间了,并且了解 extendssuper 通用边界。

我正在阅读 this post 并试图了解下限的必要性。

在我的语言中,我计划以与常规字段相同的方式处理泛型,如果您说 List<MyObject>,您可以存储 MyObject,或 [=16] 的任何子类型=].有道理吗?

在 post 上,它们具有以下 class 层次结构:

class Person implements Comparable<Person> {
  ...
}

class Student extends Person {
  ...
}

然后他们有一个排序方法:

public static <T extends Comparable<T>> void sort(List<T> list) {
  ...
}

我认为,您应该能够向此方法发送 List<Student>。作为 Student 扩展 Personcompare 方法将由它的 superclass、Person.

处理

The reason for the error message is that the compiler infers the type parameter of the sort method as T:=Student and that class Student is not Comparable<Student> . It is Comparable<Person> , but that does not meet the requirements imposed by the bound of the type parameter of method sort. It is required that T (i.e. Student ) is Comparable<T> (i.e. Comparable<Student> ), which in fact it is not.

上面的内容对我来说没有任何意义...你应该可以做到 student.compare(person),为什么这个不行?

也许是说 Student 应该实现自己的比较方法,以便 Student 在比较中有发言权?您不需要做任何特别的事情,只需重写 Person 的方法即可。您无法保证您正在与另一个 Student 进行比较,但可以使用 instanceof.

进行检查

我在这里遗漏了什么吗?

经过所有这些思考,我现在想知道 extends 的目的是什么。据我了解,在 List<MyType> 中,您只能放入 MyType,而不能放入任何子class。如上所述,这对我来说没有任何意义,您应该能够像字段一样将任何子 class 放入列表中。

我应该说清楚,不是"why doesn't it work in Java",而是"why doesn't it work in generics theory"。我刚刚标记了 java,因为那是我进行比较的地方。

第一:方法声明

public static <T extends Comparable<T>> void sort(List<T> list)

对我来说意义不大。我觉得应该是

public static <T extends Comparable<? super T>> void sort(List<T> list)

那么就可以写sort(listOfStudents)了。现在我将解释upperlower bounded wildcards:

的优点

类型参数的多态性没有转移到它的泛型类型

这意味着学生列表 (List<Student>) 不是 人员列表 (List<Person>)。像

这样的指令
List<Person> list = new List<Student>();

会在 Java 中失败。原因很简单:list.add(new Person()); 对于学生列表是非法的,但对于人员列表则不是。

上限通配符

但也许你有一个函数不关心对象是否是子类。例如:您可以使用这样的方法:

void printAll(List<Person> list)

他们只是将一些关于所有人的数据打印到标准输出。如果你有一份学生名单(List<Student> listOfStudents),你可以写:

List<Person> listOfPersons = new ArrayList<>();
for (final Student student : listOfStudents) {
    listOfPersons.add(student);
}
printAll(listOfPersons);

但您可能会发现这不是一个很好的解决方案。另一种解决方案是对 printAll:

使用 上限通配符
void printAll(List<? extends Person> list)

你可以在 printAll 中写类似 Person person = list.get(0) 的内容。但是你不能写 print.add(new Person()) 因为 list 可能是学生列表或其他东西。

下界通配符

现在在另一个方向上也是如此:假设您有一个生成一些学生并将他们放入列表中的函数。像这样:

void generateStudents(List<Student> list) {
    for (int i = 0; i < 10; ++i) {
        list.add(new Student());
    }
}

现在您有了一个人员列表 (List<Person> listOfPersons) 并希望在此列表中生成学生。你可以写

List<Student> listOfStudents = new ArrayList<>();
generateStudents(listOfStudents);
for (Student student : listOfStudents) {
    listOfPersons.add(student);
}

您可能会再次看到,这不是一个很好的解决方案。您还可以将 generateStudents 的声明更改为

void generateStudents(List<? super Student> list)

现在,您可以只写 generateStudents(listOfPersons);

我认为您的困惑可能来自这样一个事实,即 List<Student> 的元素可以通过 class Student sub[=54 这一事实相互比较=]es Person 实现了 Comparable<Person>(class Student 因此继承了 compareTo(Person o),可以用 Student 的实例调用),你仍然无法使用 List<Student>...

调用 sort 方法

问题是当Java编译器遇到语句:

sort(studentList); 

其中studentList是参数化类型List<Student>的实例,它使用类型推断来推断sort方法T的类型参数是Student,而Student不满足上限:Student extends Comparable<Student>。因此,编译器在这种情况下会抛出一个错误,告诉你推断出的类型不符合约束。

您链接到的文章向您展示了解决此问题的方法是将排序方法重写为:

public static <T extends Comparable <? super T > > void sort(List<T> list)

此方法签名放宽了对类型参数的约束,以便您可以使用 List<Student>.

调用该方法

你的最后一部分我不太清楚post:

in a List<MyType>, you can only put a MyType in, not any of it's subclasses.

如果您指的是 List<MyType> 的元素,是的,您可以将 MyType 的子类型的任何元素放入此列表中,例如 MySubType。如果您要引用一个以 List<MyType> 作为其引用类型的变量,那么不可以,您不能将对 List<MySubType> 的引用放在 List<MyType> 类型的变量中。当您考虑以下因素时,很容易看出其中的原因:

List<MyType> a;
List<MySubType> b = new ArrayList<>();
a = b; // compile-time-error, but assume OK for now
a.add(new MyType()); // Based on the type of a, this should be OK, but it's not because a is actually a reference to List<MySubType>.

我还认为您应该参考 Java Wadler 和 Naftalin 的泛型,这是对 Java (5+) 类型系统的精彩介绍。

当您根据对对象集合的观察询问 "what is the purpose of extends keyword?" 时,您应该记住的第一件事是泛型集合很棘手。我引用 Wadler/Naftalin 书中的内容(强调我的):

In Java, one type is a subtype of another if they are related by an extends or implements clause: Integer is a subtype of Number. Subtyping is transitive.

If A is a subtype of B, B is the supertype of A.

Liskov’s Substitution Principle tells us that wherever a value of one type is expected, one may provide a value of any subtype of that type: a variable of a given type may be assigned a value of any subtype of that type, and a method with a parameter of a given type may be invoked with an argument of any subtype of that type.

It’s because of violation of Liskov’s Substitution Principle (that would arise very rapidly in practice) that a List<Integer> is not a subtype of List<Number> although Integer is a subtype of Number. The other way round also does not work because List<Number> is NOT a subtype of List<Integer>.

这一段应该帮助我们理解为什么关键字 extends 对于支持继承和多态性是必不可少的,但它(有点)以通用集合的方式出现。