为什么我不能在具有多个边界的类型参数中使用类型参数?

时间:2008-10-13 10:21:58

标签: java generics constraints

所以,我理解 以下不起作用,但为什么不起作用?

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()方法给出了编译错误,“当第一个绑定是一个类型参数时,无法指定任何其他绑定的适配器&lt; E&gt;”(在Eclipse中),或者“类型参数不能跟随其他边界” (在IDEA),请选择。

显然,你只是不允许在I之前使用类型参数&,就是这样。 (在你问之前,如果你切换它们就行不通,因为不能保证I不是具体的类。)但为什么不呢?我查看了Angelika Langer的常见问题解答,找不到答案。

通常,当某些泛型限制看似随意时,这是因为您创建了一种类型系统无法实际执行正确性的情况。但我不知道哪种情况会破坏我在这里要做的事情。我可能会说它可能与类型擦除后的方法调度有关,但是只有一个add()方法,所以它不像是有任何歧义......

有人可以为我证明这个问题吗?

5 个答案:

答案 0 :(得分:27)

我也不确定为什么会有限制。您可以尝试向Java 5 Generics(主要是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)

这等于Generics之前max的签名。

似乎有理由认为Java设计人员只关心这种简单的情况并限制了交集类型的其他(更高级)用途,因为他们只是不确定它可能带来的复杂性。因此,这个设计决策的原因不一定是一个可能的安全问题(如问题所示)。

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

答案 1 :(得分:13)

禁止此事的两个可能原因:

  1. 复杂性。 Sun bug 4899305表明包含类型参数和其他参数化类型的绑定将允许比已存在的更复杂的相互递归类型。简而言之,Bruno's answer

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

    /** Contains a Comparator<String> that also implements the given type T. */
    class StringComparatorHolder<T, C extends T & Comparator<String>> {
      private final C comparator;
      // ...
    }
     
    void foo(StringComparatorHolder<Comparator<Integer>, ?> holder) { ... }

  3. 现在holder.comparatorComparator<Integer>Comparator<String>。我不清楚这会给编译器造成多大的麻烦,但显然不是很好。特别假设Comparator有这样的方法:

    void sort(List<? extends T> list);

    我们的Comparator<Integer> / Comparator<String>混合现在有两种方法具有相同的删除:

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

    由于这些原因,您无法直接指定此类型:

    <T extends Comparator<Integer> & Comparator<String>> void bar() { ... }
    java.util.Comparator cannot be inherited with different arguments:
        <java.lang.Integer> and <java.lang.String>

    由于<A extends I & Adapter<E>>允许你间接地做同样的事情,所以也是。

答案 2 :(得分:11)

以下是JLS的另一个引用:

  

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

这些尴尬的情况究竟是什么,我不知道。

答案 3 :(得分:2)

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

  

绑定由类型变量或类或接口类型T组成,可能后跟其他接口类型I1,...,In。

因此,如果您使用type参数作为绑定,则不能使用任何其他绑定,就像错误消息所示。

为何限制?我不知道。

答案 4 :(得分:0)

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

    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<?, ? extends I>来解决。

注意

这只是一个猜测,但是通常在这种用法中,您很快就需要在某个地方使用? super A? super I