[英]Why does Java have lower bounds in generics?
我正在尝试设计自己的编程语言,并且正在考虑 generics。 我做 Java 已经有一段时间了,并且知道extends
和super
通用边界。
我正在阅读这篇文章并试图了解对下限的需求。
用我的语言,我打算像常规字段一样做 generics ,如果你说List<MyObject>
,你可以存储MyObject
MyObject
任何子类型。 有道理吗?
在帖子中,它们具有以下 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
方法将由它的超类Person
处理。
错误消息的原因是编译器将排序方法的类型参数推断为 T:=Student 并且 class Student is not
Comparable<Student>
。 是Comparable<Person>
,但不满足方法排序的类型参数的边界要求。 要求T
(即Student
)是Comparable<T>
(即Comparable<Student>
),实际上它不是。
以上对我来说没有任何意义......你应该能够做student.compare(person)
,那么为什么这不起作用?
也许是说Student
应该实现自己的比较方法,以便Student
在比较中有发言权? 你不需要做任何特别的事情,只需覆盖Person
的方法。 您将无法保证您正在与另一个Student
进行比较,但可以使用instanceof
进行检查。
我在这里缺少什么吗?
经过这么多思考,我现在想知道extends
的目的是什么。 据我了解,在List<MyType>
中,您只能放入MyType
,而不是它的任何子类。 如上所述,这对我来说没有任何意义,您应该能够将任何子类像字段一样放在列表中。
我可能应该说清楚,这不是“为什么它在 Java 中不起作用”,而是“为什么它在 generics 理论中不起作用”。 我只是标记了 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)
。 现在,我将解释的上下界通配符的优势:
这意味着学生List<Student>
( List<Student>
) 不是人员List<Person>
( List<Person>
)。 像这样的指令
List<Person> list = new List<Student>();
在Java中会失败。 原因很简单: list.add(new Person());
对于学生列表是非法的,但对于人员列表则不合法。
但是也许您有一个函数,它并不关心对象是否为子类。 例如:您可能有这样的方法:
void printAll(List<Person> list)
他们只是将有关所有人的一些数据打印到标准输出。 如果您有学生List<Student> listOfStudents
( 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)
的printAll
。 但是您不能编写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<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>
可凭借该类别的事实相互比较Student
子类Person
它实现Comparable<Person>
(类Student
因此继承compareTo(Person o)
,可以使用Student
的实例调用它),您仍然无法使用List<Student>
调用sort
方法。
问题是,当Java编译器遇到以下语句时:
sort(studentList);
其中studentList
是参数化类型List<Student>
的实例,它使用类型推断来推断sort
方法T
的类型参数为Student
,并且Student
不满足上限: Student extends Comparable<Student>
。 因此,在这种情况下,编译器将引发错误,告诉您推断的类型不符合约束。
您链接到的文章向您显示解决方案是将sort方法重写为:
public static <T extends Comparable <? super T > > void sort(List<T> list)
此方法签名放宽了对type参数的约束,因此您可以使用List<Student>
调用该方法。
我对您的帖子的最后部分不太清楚:
在
List<MyType>
,您只能放入MyType
,而不能放入其任何子类。
如果要引用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>.
我还认为您应该参考Wadler和Naftalin的Java Generics,这是对Java(5+)类型系统的出色介绍。
当您问“ extends
关键字的目的是什么?”时 根据对对象集合的观察,您应该记住的第一件事是通用集合很棘手。 我引自Wadler / Naftalin的书(重点是我的):
在Java中,一种类型是另一种的子类型 (如果它们通过extends或Implements子句关联):整数是Number的子类型。 子类型化是可传递的。
如果A是B的子类型,则B是A的超类型。
里氏的替换原则告诉我们,无论一个类型的值的预期,一个可提供该类型的任何亚型的一个值:给定类型的变量可被分配该类型的任何亚型的值,并且与所涉及的方法给定类型的参数可以用该类型任何子类型的参数调用。
因为违反了Liskov的“替换原则” (在实践中会很快出现) ,所以List <Integer>不是 List <Number>的子类型,尽管Integer是Number的子类型 。 反之亦行不通,因为List <Number>不是List <Integer>的子类型。
本段应该帮助我们理解为什么关键字extends
对于支持继承和多态性必不可少,但是它(某种)以通用集合的方式出现。
我认为您的问题可以更好地表述为:“在 JAVA 中使用下限 generics 的有效用例是什么”? 我有同样的问题,当你有一个列表并且你想使用不同的类型,这些类型都是子类型或类层次结构的超类型时,使用上限是有意义的。
例如,您希望方法用 int、double 和 long 填充数字列表。 使用扩展您可以做到这一点,因为它们都是 Number 的子类型。
另一方面,如果您想将方法限制为仅使用 Integer,那么您需要定义更窄的 class 以便只允许使用 int,而不是 float、double 等。
来自java 文档:
抽象 class Number 是平台类的超类,表示可转换为基本类型 byte、double、float、int、long 和 short 的数值。
Integer class 将原始类型 int 的值包装在 object 中。 Integer 类型的 object 包含一个类型为 int 的字段。
一个更好的例子可能是您在 JVM 级别上所做的工作,因此您希望限制您的方法使用 VirtualMachineError 来获取写入日志的特定信息,而不是使用它继承的错误 class 来限制数量您写入日志的错误是非常精细的,可能与某些调试任务有关。
这些显然是人为的例子,但主题是可以用来限制方法类型参数的下限。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.