Несовместимый тип с 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));
Ответы
Ответ 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);
может быть момент времени, когда компилятор определяет новый тип списка: я полагаю, что он должен определить общий тип списка перед рассмотрением назначения. Для этого он принимает информацию, которую он должен выводить тип нового списка, независимо от назначения. Это может привести к созданию двух разных типов списков двумя вышеупомянутыми утверждениями, что приведет к наблюдаемому поведению.