Подтвердить что ты не робот

Несовместимый тип с Arrays.asList()

В следующем примере, если у меня есть несколько типов в списке, он компилируется нормально, но если у меня есть один элемент, он выбирает другой тип, который больше не может быть назначен.

// 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>>

Почему скомпилировать два элемента, но не один элемент?

BTW: Трудно найти простой пример, если вы попробуете

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);

EDIT: на основе примера Марко. В этих четырех примерах никто не компилируется, остальные образуют один и тот же список того же типа.

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));
4b9b3361

Ответ 1

Интересная проблема. Я думаю, что это происходит. Когда у вас есть два элемента, как вы показываете, тип возврата из asList является наиболее конкретным типом всех аргументов, который в вашем первом примере равен List<Reference>. Это назначение совместимо с List<? extends Reference>. Когда у вас есть один аргумент, возвращаемый тип - это конкретный тип аргумента, который не совместим с назначением, потому что генерики не являются ковариантными.

Ответ 2

Рассмотрим

    // 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 целевым типом.

Это работает при назначении или в операторе return

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

Ответ 3

В объяснении этого поведения есть две части:

  • Как изменяется тип правой части с меняющимися аргументами?
  • Почему некоторые типы 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. Левая сторона

Теперь почему мы не можем назначить первое выражение типа List<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);

это приведет к явному нарушению безопасности типа.

Ответ 4

Это интересно:

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); тип 'пересечения' явно указывается (как List) и не должен быть выведен компилятором.

Кроме того, я считаю, что Тед Хопп сказал правильно для другого случая.

EDIT:

Разница между

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

и

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

может быть момент времени, когда компилятор определяет новый тип списка: я полагаю, что он должен определить общий тип списка перед рассмотрением назначения. Для этого он принимает информацию, которую он должен выводить тип нового списка, независимо от назначения. Это может привести к созданию двух разных типов списков двумя вышеупомянутыми утверждениями, что приведет к наблюдаемому поведению.