[英]Java Generics of the form <T extends A<T>> and Eclipse Compiler
我對Java Generics有另一個有趣的問題。 我敢肯定,你們中的一些人可能有正確的答案。
語境。 首先,為了理解上下文,讓我們看看對象Animal,Mammal和Cat的定義,它們是相互定義的:
public abstract class Animal<T extends Animal<T>> {
private final int age;
public Animal(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public abstract T setAge(int age);
public static void main(String[] args) {
List<Mammal<?>> mammals = new ArrayList<Mammal<?>>();
mammals = Animal.increaseAge(mammals, 1);
Map<Integer, List<Mammal<?>>> mammalsByAge = Animal.groupByAge(mammals);
}
public static <T extends Animal<T>> List<T> increaseAge(List<T> animals, int augment) {
List<T> list = new LinkedList<T>();
for (T animal : animals) {
list.add(animal.setAge(animal.getAge() + augment));
}
return list;
}
public static <T extends Animal<T>> Map<Integer, List<T>> groupByAge(List<T> animals) {
Map<Integer, List<T>> animalsPerAge = new TreeMap<Integer, List<T>>();
animals.forEach((animal) -> {
int age = animal.getAge();
animalsPerAge.putIfAbsent(age, new LinkedList<T>());
animalsPerAge.get(age).add(animal);
});
return animalsPerAge;
}
};
abstract class Mammal<T extends Mammal<T>> extends Animal<Mammal<T>> {
public Mammal(int age) {
super(age);
}
};
class Cat extends Mammal<Cat> {
public Cat(int age) {
super(age);
}
@Override
public Cat setAge(int age) {
return new Cat(age);
}
}
后來,我會有不同的動物和哺乳動物。 此外,所有元素都必須實施“寫入時復制”策略,即更改動物的屬性會創建具有新屬性的相同類型的新動物。
最后,我需要實現兩個輔助函數, groupByAge
和increaseAge
。
問題。 最初,我意識到Eclipse IDE可以編譯和執行程序,而Maven編譯器因編譯器錯誤而失敗。
[ERROR] Failed to execute goal
org.apache.maven.plugins:maven-compiler-plugin:3.1:compile
(default-compile) on project BCBS248Calculator: Compilation failure:
Compilation failure:
[ERROR] Animal.java:[25,33] method increaseAge in class
com.ibm.ilm.bcbs248.Animal<Tcannot be applied to given types;
[ERROR] required: java.util.List<T>,int
[ERROR] found: java.util.List<com.ibm.ilm.bcbs248.Mammal<?>>,int
[ERROR] reason: inferred type does not conform to equality
constraint(s)
[ERROR] inferred: com.ibm.ilm.bcbs248.Mammal<capture#1 of ?>
[ERROR] equality constraints(s): com.ibm.ilm.bcbs248.Mammal<capture#1
of ?>,com.ibm.ilm.bcbs248.Mammal<?>
[ERROR] Animal.java:[27,68] method groupByAge in class
com.ibm.ilm.bcbs248.Animal<Tcannot be applied to given types;
[ERROR] required: java.util.List<T>
[ERROR] found: java.util.List<com.ibm.ilm.bcbs248.Mammal<?>>
[ERROR] reason: inferred type does not conform to equality
constraint(s)
[ERROR] inferred: com.ibm.ilm.bcbs248.Mammal<capture#2 of ?>
[ERROR] equality constraints(s): com.ibm.ilm.bcbs248.Mammal<capture#2
of ?>,com.ibm.ilm.bcbs248.Mammal<?>
乍一看,我認為這是一個Maven-Problem(因為Eclipse可以編譯並執行代碼),但實際上,它似乎是Eclipse編譯器中的一個bug (?) 。 即使是普通的Java編譯器也無法編譯程序,它會因類型錯誤而終止,因為程序無法驗證。 這是Java泛型的一個眾所周知的問題,即Java類型檢查器此時無法驗證程序。 這僅僅是由於靜態類型檢查器的近似。
據我所知,Eclipse使用自己的Java編譯器,即允許編譯惡意程序的原始Java編譯器的包裝器? 但是,在這種情況下,它沒有顯示任何錯誤消息? 任何人都可以驗證這是Eclipse編譯器的問題嗎?
此外,有沒有人對如何解決編碼問題有另一個優雅的想法? 作為一個簡單的單詞,我可以更改兩個輔助函數的類型簽名,如下所示:
public static <T extends Animal<?>> List<T> increaseAge(List<T> animals, int augment) {
List<T> list = new LinkedList<T>();
for (T animal : animals) {
list.add((T) animal.setAge(animal.getAge() + augment));
}
return list;
}
public static <T extends Animal<?>> Map<Integer, List<T>> groupByAge(List<T> animals) {
Map<Integer, List<T>> animalsPerAge = new TreeMap<Integer, List<T>>();
animals.forEach((animal) -> {
int age = animal.getAge();
animalsPerAge.putIfAbsent(age, new LinkedList<T>());
animalsPerAge.get(age).add(animal);
});
return animalsPerAge;
}
不幸的是,我還需要在行list.add((T) animal.setAge(animal.getAge() + augment));
添加一個類型list.add((T) animal.setAge(animal.getAge() + augment));
。 我可以使用改變后的類型簽名,但是我將擺脫類型轉換,因為它以不確定性結束。
重新添加相同類型的元素似乎不是更改類型簽名的問題。 但是,在調用setAge
某些類型的信息似乎丟失了。 也許我可以通過這種方式改變函數或對象的類型?
-
Maven 3.1版
Java版本1.8.0_201 Java(TM)SE運行時環境(版本1.8.0_201-b09)Java HotSpot(TM)64位服務器VM(版本25.201-b09,混合模式)
Eclipse IDE for Java Developers版本:Oxygen.3a版本(4.7.3a)版本號:20180405-1200
我認為這是一個Maven問題
Maven只是一個構建工具。 如果您遇到編譯錯誤,則與Maven無關。
Eclipse使用自己的Java編譯器,即允許編譯惡意程序的原始Java編譯器的包裝器
Eclipse有一個不同的編譯器,是的。 '惡意'不正確。 它支持漸進式編譯(僅在代碼更改時重新編譯代碼)。 它可以編譯和運行帶有編譯時錯誤的代碼,而不是將它們推遲到運行時異常。
任何人都可以驗證這是Eclipse編譯器的問題嗎?
正如你自己所說:“即使是普通的Java編譯器也無法編譯程序”。 所以答案是否定的。 你的程序是不正確的。
你的問題基本上源於我不認為這些是有意義的泛型類型參數
Animal<T extends Animal<T>>
Mammal<T extends Mammal<T>>
當我看到泛型類型參數時,我嘗試插入單詞'to'或'of',看看它是否有意義。
List<String>
是一個字符串列表。 Comparable<String>
是什么,可以比較字符串。 甲Listener<Message>
監聽消息。
那么什么是Mammal<Cat>
? 貓的哺乳動物? 哺乳動物對貓? 你是否以同樣的方式看待它沒有意義?
您正在嘗試使用泛型將子類的知識傳播到父類。 這不是他們想要的。
以下是刪除泛型類型參數時代碼的外觀。 有一個未經檢查的強制轉換,但我們知道任何setter都保證返回相同類型的對象,因此可以安全地忽略它。
abstract class Animal {
private final int age;
public Animal(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public abstract Animal setAge(int age);
public static <T extends Animal> List<T> increaseAge(List<T> animals, int augment) {
List<T> list = new LinkedList<>();
for (T animal : animals) {
list.add((T) animal.setAge(animal.getAge() + augment));
}
return list;
}
public static <T extends Animal> Map<Integer, List<T>> groupByAge(List<T> animals) {
Map<Integer, List<T>> animalsPerAge = new TreeMap<>();
animals.forEach((animal) -> {
int age = animal.getAge();
animalsPerAge.putIfAbsent(age, new LinkedList<>());
animalsPerAge.get(age).add(animal);
});
return animalsPerAge;
}
public static void main(String[] args) {
List<Mammal> mammals = new ArrayList<>();
mammals = Animal.increaseAge(mammals, 1);
Map<Integer, List<Mammal>> mammalsByAge = Animal.groupByAge(mammals);
}
};
abstract class Mammal extends Animal {
public Mammal(int age) {
super(age);
}
}
class Cat extends Mammal {
public Cat(int age) {
super(age);
}
@Override
public Cat setAge(int age) {
return new Cat(age);
}
}
綜上所述,問題不在於方法體。 類型錯誤表示作為參數給出的列表不符合方法的類型簽名。 最后,它歸結為具有泛型的列表的標准協方差和逆變問題。 不知怎的,我認為我可以通過使用像Animal<Mammal<T>>
這樣的嵌套泛型和具體的方法簽名來巧妙地繞過不確定性,但似乎它只將問題移植到另一個地方。
總結可能的解決方案:
可以使用通配符修復第二個方法調用( groupByAge
) ?
,像<T extends Animal<?>>
。 這是有效的,因為我們只將類型T
的元素移動到另一個類型為T
列表。
第一個方法調用( increaseByAge
)比較棘手。 我們還可以添加通配符?
但是,對於方法的類型簽名,我們在調用setAge
之后丟失了我們有T
類元素的信息( setAge
返回一個Animal<?>
類型的元素)。
另一個人想知道如何解決這個問題,也許是通過使用Animal的另一種類型簽名?
顯然,類型轉換可以解決這個問題,但是類型轉換是不可接受的(即使它們的唯一目的是讓類型檢查器開心)。
另一種可能的解決方案是在列表定義中從Mammal
中完全刪除泛型類型,如List<Mammal> mammals = new ArrayList<Mammal>();
。 然而,這也導致了未經檢查的操作,因此就出局了。
顯然,代碼還會檢查我們是否使用具體列表,如Cat
列表或Dog
列表,如List<Cat> cats = new ArrayList<Cat>();
。 但是,在這種情況下,情況需要有一個貓和狗的共同列表。
最后,仍然需要具有類型特定的increaseByAge
-methods,例如,哺乳動物列表的increaseByAge
方法,也可以在Mammal
類中實現。
public static List<Mammal<?>> increaseByAge(List<Mammal<?>> mammals, int augment) {
List<Mammal<?>> list = new LinkedList<Mammal<?>>();
for (Mammal<?> mammal : mammals) {
mammal = mammal.setAge(mammal.getAge() + augment);
list.add(mammal);
}
return list;
}
可以從方法中刪除泛型類型,並直接關注哺乳動物列表。 Unfortuantly,這需要有多個increaseByAge
方法,每個組的動物。
我仍然願意接受其他解決方案。 也許有人也會看到另一種想法,即如何編寫對象的泛型類型以使其更容易。
盡管有關泛型類型的所有討論,但仍然提到這似乎是Eclipse類型檢查器中的一個錯誤。 標准Java編譯器無法編譯此代碼,而Eclipse IDE編譯代碼也不會顯示任何類型錯誤。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.