37

在下面的示例中,如果列表中有多种类型,它可以编译,但如果我有一个元素,它会选择另一种不再可分配的类型。

// compiles fine
List<Class<? extends Reference>> list = Arrays.asList(SoftReference.class, WeakReference.class);
// but take an element away and it no longer compiles.
List<Class<? extends Reference>> list2 = Arrays.asList(WeakReference.class);
// without giving the specific type desired.
List<Class<? extends Reference>> list3 = Arrays.<Class<? extends Reference>>asList(WeakReference.class);

我确信对此有一个合乎逻辑的解释,但它逃脱了我。

    Error:Error:line (30)error: incompatible types
required: List<Class<? extends Reference>>
found:    List<Class<WeakReference>>

为什么有两个元素可以编译而一个元素不能编译?

顺便说一句:很难找到一个简单的例子,如果你尝试

List<Class<? extends List>> list = Arrays.asList(ArrayList.class, LinkedList.class);

    Error:Error:line (28)error: incompatible types
required: List<Class<? extends List>>
found:    List<Class<? extends INT#1>>
where INT#1 is an intersection type:
INT#1 extends AbstractList,Cloneable,Serializable

这也不能编译(它甚至不会解析)

List<Class<? extends AbstractList & Cloneable & Serializable>> list = Arrays.asList(ArrayList.class, LinkedList.class);

Error:Error:line (30)error: > expected
Error:Error:line (30)error: ';' expected

但这编译得很好

static abstract class MyList<T> implements List<T> { }
List<Class<? extends List>> list = 
        Arrays.asList(ArrayList.class, LinkedList.class, MyList.class);
List<Class<? extends List>> list = 
        Arrays.<Class<? extends List>>asList(ArrayList.class, LinkedList.class);

编辑:基于马尔科的例子。在这四个示例中,一个不编译,其余的生成相同类型的相同列表。

List<Class<? extends Reference>> list = new ArrayList<>();
list.add(SoftReference.class);
list.add(WeakReference.class);
list.add(PhantomReference.class);

List<Class<? extends Reference>> list = new ArrayList<>(
     Arrays.asList(SoftReference.class));
list.add(WeakReference.class);
list.add(PhantomReference.class);

List<Class<? extends Reference>> list = new ArrayList<>(
     Arrays.asList(SoftReference.class, WeakReference.class));
list.add(PhantomReference.class);

List<Class<? extends Reference>> list = new ArrayList<>(
     Arrays.asList(SoftReference.class, WeakReference.class, PhantomReference.class));
4

4 回答 4

14

有趣的问题。我认为正在发生的事情是这样的。当您有两个像您展示的元素时,返回类型 fromasList是所有参数中最具体的类型,在您的第一个示例中是List<Reference>. 这与赋值兼容List<? extends Reference>。当你有一个参数时,返回类型是参数的特定类型,它不是赋值兼容的,因为泛型不是协变的。

于 2012-12-30T15:08:31.563 回答
10

考虑

    // ok
    List<Object> list3 = Arrays.asList(new Object(), new String());
    // fail
    List<Object> list4 = Arrays.asList(new String());

第二个示例尝试将 a 分配List<String>给 a List<Object>,但失败了。

第二个例子可以工作,如果 javac 查看周围的上下文,考虑到目标类型,并推断出T=Object在这里可以工作。Java 8 可能会这样做(我不确定)

只有在一种情况下,javac(java 5)将使用上下文信息进行类型推断,请参阅http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12。 2.8

我们可以利用它来解决问题

public static <R, T extends R> List<R> toList(T... elements)
{
    return Arrays.asList((R[])elements);
}

现在可以编译它们:

    List<Object> list4 = toList(new String());

    List<Class<? extends Reference>> list = toList(SoftReference.class, WeakReference.class);

    List<Class<? extends Reference>> list2 = toList(WeakReference.class);

这是因为R无法从参数类型推断,并且方法结果在赋值上下文中,所以 javac 尝试R通过目标类型推断。

这适用于赋值或返回语句

List<Class<? extends Reference>> foo()
{
    return toList(WeakReference.class);  // "subject to assignment conversion"
}

否则将无法正常工作

void bar(List<Class<? extends Reference>> list){...}

bar( toList(WeakReference.class) ); // fail; R not inferred
于 2012-12-30T17:04:37.637 回答
4

对这种行为的解释分为两部分:

  1. 右手边的类型如何随着参数的变化而变化?
  2. 为什么有些 RHS 类型与 LHS 类型不兼容?

1. 右手边

的签名asList

<T> List<T> asList(T... a)

这意味着必须将所有参数合并为一个类型T,这是所有参数类型中最常见的特定类型。在这种特殊情况下,我们有

asList(WeakReference.class) -> List<Class<WeakReference>>

asList(WeakReference.class, SoftReference.class) 
   -> List<Class<? extends Reference>>

这两个都很明显。

2. 左侧

现在,为什么我们不能将 type 的第一个表达式分配给 typeList<Class<WeakReference>>的变量List<Class<? extends Reference>>?理解为什么规则必须如此的最好方法是反证法。考虑以下:

  • List<Class<? extends Reference>>add(Class<? extends Reference>)
  • List<Class<WeakReference>>add(Class<WeakReference>)

现在,如果 Java 允许您将一个分配给另一个:

List<Class<WeakReference>> lw = new ArrayList<>();
List<Class<? extends Reference>> lq = lw;
lq.add(PhantomReference.class);

这将导致明显违反类型安全。

于 2012-12-30T16:32:34.937 回答
2

这是有趣的:

where INT#1 is an intersection type:
INT#1 extends AbstractList,Cloneable,Serializable

也许这就是(某些)问题的原因?

元素的交集类型可能不是唯一确定的。当您声明自己的列表MyList<T> implements List<T>时,数组的交集类型被确定为List<T>

当使用Arrays.<Class<? extends List>>asList(ArrayList.class, LinkedList.class);'intersection' 类型时,明确说明(as List)并且不需要由编译器推断。

除此之外,我相信 Ted Hopp 所说的对另一种情况是正确的。

编辑:

和...之间的不同

List<Class<? extends Reference>> list2 = Arrays.asList(WeakReference.class);

List<Class<? extends Reference>> list3 = Arrays.<Class<? extends Reference>>asList(WeakReference.class);

可能是编译器确定新列表类型的时间点:我认为它需要在考虑分配之前确定列表的泛型类型。为此,它需要它必须推断新列表类型的信息,而不考虑分配。这可能会导致上述两个语句创建两种不同类型的列表,从而导致观察到的行为。

于 2012-12-30T15:16:36.250 回答