<T extends A<T>> 和继承形式的 Java 泛型

Posted

技术标签:

【中文标题】<T extends A<T>> 和继承形式的 Java 泛型【英文标题】:Java Generics of the form <T extends A<T>> and Inheritance 【发布时间】:2018-10-04 12:07:20 【问题描述】:

我对 Java 泛型有一个棘手的问题,或者我可能只见树木不见森林。

我有三个类,A、B 和 C,如下所示。

abstract class A<T extends A<T>> 
    abstract T sefl();
;

abstract class B<T extends B<T>> extends A<T> 
;

class C extends B<C> 
    @Override
    C sefl() 
        return this;
    

后来我有了不同版本的 B 和不同版本的 C。此外,我有一个功能测试,它应该接受 B(或其表亲之一)的列表。或者一般来说,它应该接受从 A 继承的任何元素列表。不幸的是,我需要知道函数体中列表元素的类型,即 a.self 可以返回的最上面的类型(或类型 T)。功能测试如下:

static <T extends A<T>> void test(List<T> list) 

    for (A<T> a : list) 
        @SuppressWarnings("unused")
        T t = a.sefl();
    


现在,用 C 的作品列表调用 test。

List<C> cs = new LinkedList<C>();
test(cs);

但是

List<B> bs = new LinkedList<B>();
test(bs);

导致需要类型参数的警告并且

List<B<?>> bs = new LinkedList<B<?>>();
test(bs);

无效。我的错误在哪里,或者如何创建功能测试接受的 B 列表?


关于这个问题背后的动机的一些话。 A、B 和 C 类(或 Animal、Mammal 和 Cat)实现了一个树状数据结构,其中每个类都用一些属性扩展了该结构。通常,所有超类都是抽象的,您只能从叶类创建实例,例如猫。现在,困难在于这些类实现了写时复制策略 (COW),即修改对象会创建并返回具有修改后属性的自身新实例。

例如,假设所有动物都有年龄属性。您可以在 Animal 中轻松定义此属性,并且您可以提供一个 getter 方法来返回年龄。

abstract class Animal<T extends Animal<T>> 
    private int age;
    public int getAge(int age) 
        return age;
    
;

但是,你如何定义setter方法呢?可以这样写:

public abstract Animal setAge();

这要求(至少)每个非抽象元素都必须实现 setter 函数。例如:

class Cat extends Mammal<C> 

    @Override
    public Animal setAge(int age) 
        return new Cat(/* .. */);
    

请记住,当我们实施 COW 策略时,我们必须创建一个新实例。因此,在 setter 函数中(例如在 Cat 中实现),我们返回一个新时代的新猫。对 Cat 元素调用 cat.setAge(4) 会返回一个新的 Cat。不幸的是,由于类型签名,我们现在才从 setAge 返回一个 Animal,即使我们直接在 Cat 上调用它也是如此。在调用 setAge 时,泛型的扭曲有助于揭示具体类型。所以,我们可以这样构造 Animal:

abstract class Animal<T extends Animal<T>> 
    private int age;
    public int getAge(int age) 
        return age;
    
    public abstract T setAge();
;

在 Cat 中我们可以说:

class Cat extends Mammal<C> 

    @Override
    public Cat setAge(int age) 
        return new Cat(/* .. */);
    


那么,回到问题上来。你的权利,使用List&lt;? extends Animal&lt;?&gt;&gt; 作为列表的类型,但不幸的是,我需要一些方法来了解元素的类型。或者更具体一点:功能测试必须用新元素替换旧元素。例如:

static void test2(List<? extends Animal<?>> list) 
    for (Animal<?> animal : list) 
        @SuppressWarnings("unused")
        Animal<?> a = animal.setAge(4711);
        list.add(a);
    

不幸的是,列表扩展名 list.add(a);是不适用于此签名的语句。

【问题讨论】:

顺便说一句:将B的类型更改为:抽象类B> extends A> ;函数 test 接受 List> bs; 形式的列表,但是函数 test 没有记录器接受 C 的 ala List cs; 的列表。 不要将更多信息放入 cmets。不仅因为让它们正确“格式化”需要一些知识。始终更新您的问题,而不是将更多信息放入破碎的 cmets。 你到底想用这个做什么?你可以调用test(new LinkedList&lt;&gt;()),但这可能没多大用处。 我无法想象 A、B 和 C 可能是什么,或者在什么情况下您想强制执行这些约束。你能提供一个更好的例子吗? (虽然没有DefaultAbstractConfigurationManagerFactorys ;)) 这是一个非常简单的例子。在实践中,我需要修改元素(返回自身的副本),并且需要用列表中的新元素替换旧元素。 【参考方案1】:

嗯,它们是两种截然不同的实现方式:

class C ...

class B<T extends B<T>> ...

C 类没有声明任何泛型类型。


类名的简单字母在这里有点混乱,所以让我们这样做:

abstract class Animal<T extends Animal<T>> 
    abstract T sefl();
;

abstract class Mammal<T extends Mammal<T>> extends Animal<T> 
;

class Cat extends Mammal<Cat> 
    @Override
    Cat sefl() 
        return this;
    


所以:

List<Cat> catList = new LinkedList<>();

效果很好,因为不涉及泛型类型。编译器确定

Cat extends Mammal<Cat> ( == Cat extends Animal<Cat> )

&lt;T extends Animal&lt;T&gt;&gt;的范围内

另一方面

List<Mammal> mammalList = new LinkedList<>();
test(mammalList); // ok, but mammal list of what???

编译器无法匹配有界类型。

其实Mammal&lt;T extends Mammal&lt;T&gt;&gt; extends Animal&lt;T&gt;&lt;T extends Animal&lt;T&gt;&gt;没有任何关系。

即使提供通配符,您也永远无法将List&lt;Mammal&lt;?&gt; 传递给test。方法签名拒绝它!


一种可能的解决方案:

更通用的测试方法

static void test2(List<? extends Animal<?>> list) 
    for (Animal<?> animal : list) 
        Animal a = animal.sefl();
    

可以与不同的List 类型一起使用:

List<? extends Mammal<?>> bs = new LinkedList<>();
test2(bs);

List<Cat> catList = new LinkedList<>();
test2(catList);

List<Animal<Cat>> animalList = new LinkedList<>();
test2(animalList);

Java 版本:

java 9.0.4
Java(TM) SE Runtime Environment (build 9.0.4+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.4+11, mixed mode) 

【讨论】:

我们不能把 test2 写成static &lt;T extends Animal&lt;?&gt;&gt; void test2(List&lt;T&gt; list) for(Animal&lt;?&gt; a : list) //code 吗? 很遗憾没有。执行语句'list.add(a);'我们必须知道元素的类型是 T。 @slemoine 是的,你可以,你的用法也是正确的。请记住,正如我所写,我的解决方案是一个可能的解决方案 @slemoine 好吧,你不能。我刚刚看到问题更新 @MarkoPacak 我也不 ;)。我仍然坚持您对原始问题的第一个答案。谢谢

以上是关于<T extends A<T>> 和继承形式的 Java 泛型的主要内容,如果未能解决你的问题,请参考以下文章

如何理解 Java 中的 lt;T extends Comparable<

java中泛型限定<? extends Comparable<? super T>> 和 <T extends Comparable<? super T>&g

为啥打字稿不能用 <T extends Person, K extends keyof T> generic 正确推断 T[K]?

Java泛型的应用——T extends Comparable<? super T;

Java泛型<? extends T>和<? super T>

请教Java 大神一行代码:“<? extends T>”的用法是啥?