簡體   English   中英

為什么 Java 在 generics 中有下限?

[英]Why does Java have lower bounds in generics?

我正在嘗試設計自己的編程語言,並且正在考慮 generics。 我做 Java 已經有一段時間了,並且知道extendssuper通用邊界。

我正在閱讀這篇文章並試圖了解對下限的需求。

用我的語言,我打算像常規字段一樣做 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擴展Personcompare方法將由它的超類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> listOfStudentsList<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> listOfPersonsList<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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM