【问题标题】:Why can't I use a type argument in a type parameter with multiple bounds?为什么我不能在具有多个边界的类型参数中使用类型参数?
【发布时间】:2010-09-16 20:47:57
【问题描述】:

所以,我了解以下行不通,但为什么行不通?

interface Adapter<E> {}

class Adaptulator<I> {
    <E, A extends I & Adapter<E>> void add(Class<E> extl, Class<A> intl) {
        addAdapterFactory(new AdapterFactory<E, A>(extl, intl));
    }
}

add() 方法给了我一个编译错误,“当第一个绑定是类型参数时,不能指定任何额外的绑定适配器”(在 Eclipse 中),或者“类型参数不能被其他边界跟随”(在IDEA),任君挑选。

很明显,您只是不允许在&amp; 之前使用类型参数I,仅此而已。 (在你问之前,如果你切换它们是行不通的,因为不能保证I 不是一个具体的类。)但为什么不呢?我浏览了 Angelika Langer 的常见问题解答,但找不到答案。

通常,当某些泛型限制看起来很随意时,这是因为您创建了类型系统实际上无法强制正确性的情况。但我不知道什么情况会破坏我在这里尝试做的事情。我会说也许它与类型擦除后的方法调度有关,但只有一个add() 方法,所以它不像有任何歧义......

有人可以为我演示一下这个问题吗?

【问题讨论】:

    标签: java generics constraints


    【解决方案1】:

    取缔此行为的两个可能原因:

    1. 复杂性。 JDK-4899305 建议包含类型参数和其他参数化类型的边界将允许比已经存在的更复杂的相互递归类型。简而言之,Bruno's answer

    2. 指定非法类型的可能性。具体来说,extending a generic interface twice with different parameters。我想不出一个非人为的例子,但是:

      /** 包含一个也实现给定类型 T 的 Comparator。 */
      类 StringComparatorHolder> {
      私有最终 C 比较器;
      // ...
      }
      
      void foo(StringComparatorHolder, ?> holder) { ... }

    现在holder.comparatorComparator&lt;Integer&gt;Comparator&lt;String&gt;。我不清楚这会给编译器带来多少麻烦,但这显然不好。特别假设Comparator 有这样的方法:

    void sort(List extends T> list);

    我们的Comparator&lt;Integer&gt; / Comparator&lt;String&gt; 混合现在有两种具有相同擦除的方法:

    void sort(List extends Integer> list);
    void sort(List extends String> list);

    由于这些原因,您不能直接指定这样的类型:

     & Comparator> void bar() { ... }
    java.util.Comparator 不能用不同的参数继承:
        

    由于&lt;A extends I &amp; Adapter&lt;E&gt;&gt; 允许你间接地做同样的事情,它也被淘汰了。

    【讨论】:

    • 有趣。不得不考虑一下。无论如何,编译器似乎仍然会捕捉到它——在 foo() 的声明中。但也许有一个更人为的例子可以让它更清楚。 :) James Iry 说它适用于 Scala:chrononaut.org/showyourwork/?p=52#comment-46
    • @Pacerier,谢谢。不幸的是,我现在似乎找不到它的实时副本:(
    【解决方案2】:

    我遇到了同样的问题,并找到了一个可行的解决方案:

        interface Adapter<E>
        {}
    
        interface Adaptulator<I>
        {
            void add(Container<?, ? extends I> container);
        }
    
        static final class Container<E, I extends Adapter<E>>
        {
            public final Class<E> extl;
            public final Class<I> intl;
    
            public Container(Class<E> extl, Class<I> intl)
            {
                this.extl = extl;
                this.intl = intl;
            }
        }
    

    为什么会起作用

    要理解这一点,我们需要说明我们的要求:

    1. 让两个不同的泛型在某事上保持同步。在您的情况下,这是 E
    2. 两个泛型中的一个需要有一些额外的继承,这里是 I

    创建一个额外的类可以通过创建一个紧密的上下文来满足这两个要求。

    1. 一个额外的要求(但可能是最重要的)是需要一个泛型方法(没有过多地绑定到我们的类)。

    这是通过许可参数Container&lt;?, ? extends I&gt;解决的。

    注意

    这只是一个猜测,但在这种用法中,一般来说,你很快就需要一个? super A? super I 某个地方。

    【讨论】:

      【解决方案3】:

      这是JLS的另一句话:

      绑定的形式受到限制(只有第一个元素可以是类或类型变量,并且绑定中只能出现一个类型变量)以排除某些尴尬情况的出现

      那些尴尬的情况到底是什么,我不知道。

      【讨论】:

      • 也许他们也不知道?
      • 这句话实际上似乎鼓励在类型交集中使用类型变量,而不是禁止它。我认为这段文档很有误导性。
      【解决方案4】:

      我也不确定为什么会有限制。您可以尝试向 Java 5 泛型的设计者(主要是 Gilad Bracha 和 Neal Gafter)发送一封友好的电子邮件。

      我的猜测是,他们只想支持绝对最小值 intersection types(本质上就是多重边界),以使语言不会比需要的更复杂。交集不能用作类型注解;只有当交集作为类型变量的上界出现时,程序员才能表达交集。

      为什么甚至支持这种情况?答案是多个边界允许您控制擦除,这允许在生成现有类时保持二进制兼容性。正如 Naftalin 和 Wadler 在book 的第 17.4 节中所解释的,max 方法在逻辑上将具有以下签名:

      public static <T extends Comparable<? super T>> T max(Collection<? extends T> coll)
      

      但是,这会删除为:

      public static Comparable max(Collection coll)
      

      max的历史签名不匹配,导致老客户端崩溃。 对于多个边界,只有最左边的边界被认为是擦除,所以如果max 被赋予以下签名:

      public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
      

      那么其签名的擦除就变成了:

      public static Object max(Collection coll)
      

      这等于泛型之前max的签名。

      Java 设计者只关心这种简单的情况并限制交集类型的其他(更高级)使用似乎是合理的,因为他们只是不确定它可能带来的复杂性。所以这个设计决定的原因不一定是一个可能的安全问题(正如问题所暗示的那样)。

      upcoming OOPSLA paper 中有关泛型的交集类型和限制的更多讨论。

      【讨论】:

      • 其实,如果多重边界的目的是控制擦除,那完全有道理,因为我只是要擦除到Object。
      【解决方案5】:

      这可能没有回答根本问题,只是想指出规范明确禁止它。谷歌搜索错误信息将我带到this blog entry,它进一步指向jls 4.4

      绑定由类型变量或类或接口类型 T 组成,可能后跟进一步的接口类型 I1 ,...,In。

      因此,如果您使用类型参数作为绑定,则不能使用任何其他绑定,正如错误消息所述。

      为什么要限制?我不知道。

      【讨论】:

      • 因为我可能是一个班级?仅仅因为 A 扩展了 I 并不意味着 I 是一个接口(可以使 A 成为一个接口),但是 A 很容易成为 I 的子类,这是规范所禁止的
      • 如果我是班级有什么问题? Adapter 已知是一个接口。
      猜你喜欢
      • 1970-01-01
      • 2022-11-16
      • 2011-05-18
      • 2016-05-04
      • 1970-01-01
      • 2014-12-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-01
      相关资源
      最近更新 更多