【问题标题】:Java Generics Puzzler, extending a class and using wildcardsJava Generics Puzzler,扩展类并使用通配符
【发布时间】:2015-05-21 21:48:22
【问题描述】:

我一直在反对这个问题一段时间,并认为也许一些新的眼睛会看到这个问题;感谢您的宝贵时间。

import java.util.*;

class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<T>> {}

class Base {}
class Derived extends Base {}

public class Test {
  public static void main(String[] args) {
    ArrayList<Tbin<? extends Base>> test = new ArrayList<>();
    test.add(new Tbin<Derived>());

    TbinList<? extends Base> test2 = new TbinList<>();
    test2.add(new Tbin<Derived>());
  }
}

使用Java 8。在我看来,在test 中直接创建容器等同于test2 中的容器,但是编译器说:

Test.java:15: error: no suitable method found for add(Tbin<Derived>)
    test2.add(new Tbin<Derived>());
         ^

如何写TbinTbinList 以便最后一行可以接受?

请注意,我实际上将添加键入的 Tbins,这就是我在最后一行指定 Tbin&lt;Derived&gt; 的原因。

【问题讨论】:

  • 在 Eclipse 中,错误消息是 The method add(Tbin&lt;capture#1-of ? extends Base&gt;) in the type ArrayList&lt;Tbin&lt;capture#1-of ? extends Base&gt;&gt; is not applicable for the arguments (Tbin&lt;Derived&gt;)
  • 试试这个ArrayList&lt;Tbin&lt;? extends Base&gt;&gt; test3 = new TbinList&lt;&gt;();。确实很有趣。
  • @Radiodef Eclipse 显示错误消息“无法推断 TbinList 的类型参数”。
  • @TNT 是的。我很想说没有办法解决它。该错误证明无法将TbinList 转换为ArrayList&lt;Tbin&lt;? extends Base&gt;&gt;
  • Related。虽然不确定这是一个确切的骗局。

标签: java generics bounded-wildcard


【解决方案1】:

这是因为capture conversion 的工作方式:

存在从参数化类型 G&lt;T<sub>1</sub>,...,T<sub>n</sub>&gt; 到参数化类型 G&lt;S<sub>1</sub>,...,S<sub>n</sub>&gt; 的捕获转换,其中,对于 1 ≤ i ≤ n

  • 如果T<sub>i</sub>? extends B<sub>i</sub> 形式的通配符类型参数,那么S<sub>i</sub> 是一个新类型变量[...]。

捕获转换不会递归应用。

注意结束位。所以,这意味着,给定这样的类型:

    Map<?, List<?>>
//      │  │    └ no capture (not applied recursively)
//      │  └ T2 is not a wildcard
//      └ T1 is a wildcard

仅捕获“外部”通配符。 Map 键通配符被捕获,但List 元素通配符未被捕获。这就是为什么,例如,我们可以添加到List&lt;List&lt;?&gt;&gt;,但不能添加到List&lt;?&gt;。通配符的位置很重要。

把它带到TbinList,如果我们有一个ArrayList&lt;Tbin&lt;?&gt;&gt;,通配符在它不会被捕获的地方,但如果我们有一个TbinList&lt;?&gt;,通配符在它得到的地方捕获。

正如我在 cmets 中提到的,一个非常有趣的测试是:

ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();

我们得到这个错误:

error: incompatible types: cannot infer type arguments for TbinList<>
    ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();
                                                        ^
    reason: no instance(s) of type variable(s) T exist so that
            TbinList<T> conforms to ArrayList<Tbin<? extends Base>>

所以没有办法让它按原样工作。需要更改其中一个类声明。


另外,这样想吧。

假设我们有:

class Derived1 extends Base {}
class Derived2 extends Base {}

而且由于通配符允许子类型化,我们可以这样做:

TbinList<? extends Base> test4 = new TbinList<Derived1>();

我们应该能够将Tbin&lt;Derived2&gt; 添加到test4 吗?不,这将是堆污染。我们最终可能会在 TbinList&lt;Derived1&gt; 中浮动 Derived2s。

【讨论】:

  • 我的answer 打破了你的陈述,用一种帮助方法来防止捕获。
  • @Ilya_Gazman 您的答案使用未经检查的演员表,不适用于TbinList&lt;? extends Base&gt;
  • 我现在编辑它。这个想法是一样的。为什么在这种情况下未经检查的强制转换不好?
  • @Ilya_Gazman 看看这个使用它造成堆污染的例子:ideone.com/bpKdMy。它不是类型安全的。 stackoverflow.com/a/2745301/2891664 所以,是的,我们当然可以通过未经检查的强制转换来做任何我们想做的事情。这是题外话。这当然不会使我的答案无效。
  • 有趣的是,在TbinList&lt;? extends Base&gt; test2 = new TbinList&lt;&gt;(); 中,推断出的 T 是什么?好吧,T=Base :) 所以 test2 真的是TbinList&lt;Base&gt;,加上Tbin&lt;Derived&gt; 肯定不好。
【解决方案2】:

TbinList的定义替换为

class TbinList<T> extends ArrayList<Tbin<? extends T>> {}

并用

定义test2
TbinList<Base> test2 = new TbinList<>();

反而会解决问题。

根据您的定义,您最终会得到 ArrayList&lt;Tbin&lt;T&gt;&gt;,其中 T 是扩展 Base 的任何固定类。

【讨论】:

  • 这是合理的,假设他们永远不需要例如TbinList 等效于 ArrayList&lt;Tbin&lt;Base&gt;&gt;
【解决方案3】:

您正在使用有界通配符 (TbinList&lt;? extends Base&gt;&gt; ...)。此通配符将阻止您将任何元素添加到列表中。如果您想了解更多信息,请参阅文档中关于 Wildcards 的部分。

【讨论】:

  • 奇怪,复制粘贴就行了。给我一秒钟,我会解决它
  • 解决了,谢谢!
  • @Radiodef 好吧,我想我应该在复制粘贴时多加注意。又错线了。谢谢指出
  • 该页面描述了我遇到的相同问题(在页面底部),但它没有提供解决方案。
  • @user1677663 好吧,它清楚地表明有界通配符将阻止写入列表。所以解决方案很明显:删除有界通配符或将其替换为Base。实际上,在这种情况下,通配符甚至根本不需要
【解决方案4】:

您可以如下定义泛型类型:

class Tbin<T> extends ArrayList<T> {}
class TbinList<K, T extends Tbin<K>> extends ArrayList<T> {}

然后你会创建这样的实例:

TbinList<? extends Base, Tbin<? extends Base>> test2 = new TbinList<>();
test2.add(new Tbin<Derived>());

【讨论】:

    【解决方案5】:

    好的,答案如下:

    import java.util.*;
    
    class Tbin<T> extends ArrayList<T> {}
    class TbinList<T> extends ArrayList<Tbin<? extends T>> {}
    
    class Base {}
    class Derived extends Base {}
    
    public class Test {
      public static void main(String[] args) {
    
        TbinList<Base> test3 = new TbinList<>();
        test3.add(new Tbin<Derived>());
    
      }
    }
    

    正如我所料,我一看到它就很明显。但是为了到达这里,有很多挣扎。如果只看工作代码,Java 泛型似乎很简单。

    谢谢大家,作为一个共鸣板。

    【讨论】:

    • 如果您是从 tynn 的回答中得到的,您应该考虑接受它。 meta.stackexchange.com/questions/5234/…
    • 我没有。看时间。
    • 它显示了 tynn 的答案,首先贴在我这边。无论如何,这只是一个例行的评论。有时用户会因为不知道接受而转发。
    • 接受其中一个答案。你可以接受自己的。
    【解决方案6】:

    您不能向TbinList&lt;? extends Base&gt; 添加任何对象,不能保证您将哪些对象插入到列表中。当您使用通配符extends时,它应该从test2读取数据

    如果您声明为TbinList&lt;? extends Base&gt;,这意味着您它是类 Base 或类 Base 本身的任何子类,并且当您初始化它时,您使用菱形而不是具体的类名,这会使您的 test2 不明显,这使得很难说出可以插入哪些对象。我的建议是避免这样的声明是危险的,它可能没有编译错误但它是可怕的代码,你可能会添加一些东西,但你也可能添加会破坏你的代码的错误的东西。

    【讨论】:

    • 其实可以。 test2.add(new Tbin&lt;&gt;()); 编译良好,并且在我前后打印test2 时清楚地添加了一个空列表。
    • 当我说 you cannot 时,我的意思是添加不明确类型的对象很危险 :) 我认为我应该使用“你最好不要”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-23
    • 2014-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多