为什么 Java 在泛型中有下限?
Why does Java have lower bounds in generics?
我正在尝试设计自己的编程语言,并且正在考虑泛型。我已经做 Java 有一段时间了,并且了解 extends
和 super
通用边界。
我正在阅读 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
扩展 Person
,compare
方法将由它的 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)
了。现在我将解释upper和lower 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
对于支持继承和多态性是必不可少的,但它(有点)以通用集合的方式出现。
我正在尝试设计自己的编程语言,并且正在考虑泛型。我已经做 Java 有一段时间了,并且了解 extends
和 super
通用边界。
我正在阅读 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
扩展 Person
,compare
方法将由它的 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 isComparable<Person>
, but that does not meet the requirements imposed by the bound of the type parameter of method sort. It is required thatT
(i.e.Student
) isComparable<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)
了。现在我将解释upper和lower 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 aMyType
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
对于支持继承和多态性是必不可少的,但它(有点)以通用集合的方式出现。