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()方法给了我一个编译错误,“当第一个绑定是类型参数时,不能指定任何额外的绑定 Adapter<E>”(在 Eclipse 中),或者“类型参数不能被其他边界跟随”(在 IDEA 中),请选择.

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

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

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

4

5 回答 5

35

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

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

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

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

但是,这会删除:

public static Comparable max(Collection coll)

这与 的历史签名不匹配max,并导致旧客户端中断。对于多个边界,只有最左边的边界被考虑用于擦除,因此 ifmax被赋予以下签名:

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

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

public static Object max(Collection coll)

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

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

在即将发布的 OOPSLA 论文中对泛型的交叉类型和限制进行更多讨论。

于 2008-10-13T12:25:15.343 回答
16

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

  1. 复杂。 JDK-4899305建议包含类型参数和其他参数化类型的边界将允许比现有的更复杂的相互递归类型。简而言之,布鲁诺的回答

  2. 指定非法类型的可能性。具体来说,使用不同的参数扩展一个通用接口两次。我想不出一个非人为的例子,但是:

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

现在holder.comparator是 aComparator<Integer>和 a Comparator<String>。我不清楚这会给编译器带来多少麻烦,但这显然不好。特别假设Comparator有这样的方法:

无效排序(列表<?扩展 T> 列表);

我们的Comparator<Integer>/ Comparator<String>hybrid 现在有两种相同擦除的方法:

void sort(List<? extends Integer> list);
无效排序(列表<?扩展字符串>列表);

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

<T extends Comparator<Integer> & Comparator<String>> void bar() { ... }
java.util.Comparator 不能被不同的参数继承:
    <java.lang.Integer> 和 <java.lang.String>

由于<A extends I & Adapter<E>>允许您间接地做同样的事情,它也被淘汰了。

于 2008-10-17T03:16:31.610 回答
12

这是JLS的另一句话:

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

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

于 2008-10-13T15:55:25.183 回答
2

这可能没有回答根本问题,而只是想指出规范明确禁止它。谷歌搜索错误消息将我带到这个博客条目,进一步指向jls 4.4

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

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

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

于 2008-10-13T11:33:49.737 回答
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. 一个额外的要求(但可能是最重要的)是需要一个泛型方法(不要过多地绑定到我们的类)。

这是通过 permissive 参数解决的Container<?, ? extends I>

笔记

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

于 2020-03-06T22:05:54.363 回答