簡體   English   中英

Java泛型的形式 <T extends A<T> &gt;和Eclipse編譯器

[英]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);
    }
}

后來,我會有不同的動物和哺乳動物。 此外,所有元素都必須實施“寫入時復制”策略,即更改動物的屬性會創建具有新屬性的相同類型的新動物。

最后,我需要實現兩個輔助函數, groupByAgeincreaseAge

問題。 最初,我意識到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.

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