java - parameter - Tipo incompatible con Arrays.asList()
java generics parameter (4)
Considerar
// ok
List<Object> list3 = Arrays.asList(new Object(), new String());
// fail
List<Object> list4 = Arrays.asList(new String());
El segundo ejemplo intenta asignar una List<String>
a una List<Object>
, que falla.
El segundo ejemplo podría funcionar, si javac mira el contexto circundante, toma en cuenta el tipo de destino y deduce que T=Object
funcionaría aquí. Java 8 probablemente hará eso (no estoy seguro)
Solo en una situación, javac (de java 5) usará información contextual para la inferencia de tipo, ver http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.8
Podemos aprovechar eso para hacer una solución
public static <R, T extends R> List<R> toList(T... elements)
{
return Arrays.asList((R[])elements);
}
Ahora pueden compilarse:
List<Object> list4 = toList(new String());
List<Class<? extends Reference>> list = toList(SoftReference.class, WeakReference.class);
List<Class<? extends Reference>> list2 = toList(WeakReference.class);
Esto se debe a que R
no se puede inferir de los tipos de argumentos, y el resultado del método se encuentra en un contexto de asignación, por lo que javac intenta inferir R
por el tipo de destino.
Esto funciona en asignación, o en una declaración de devolución
List<Class<? extends Reference>> foo()
{
return toList(WeakReference.class); // "subject to assignment conversion"
}
No funcionará de otra manera
void bar(List<Class<? extends Reference>> list){...}
bar( toList(WeakReference.class) ); // fail; R not inferred
En el siguiente ejemplo, si tengo varios tipos en la lista compila bien, pero si tengo un elemento, elige un tipo diferente que ya no se puede asignar.
// 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);
Estoy seguro de que hay una explicación lógica para esto, pero se me escapa.
Error:Error:line (30)error: incompatible types
required: List<Class<? extends Reference>>
found: List<Class<WeakReference>>
¿Por qué tener dos elementos compilar pero un elemento no?
Por cierto: es difícil encontrar un ejemplo simple, si lo intentas
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
Esto tampoco compila (ni siquiera analizará)
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
pero esto compila bien
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);
EDITAR: Basado en el ejemplo de Marko. En estos cuatro ejemplos, uno no compila, el resto produce la misma lista del mismo tipo.
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));
Esto es interesante:
where INT#1 is an intersection type:
INT#1 extends AbstractList,Cloneable,Serializable
Tal vez esa es la causa de (algunos de) los problemas?
El tipo de intersección de los elementos puede no estar determinado de manera única. Cuando declara su propia lista MyList<T> implements List<T>
, el tipo de intersección de la matriz se determina como List<T>
.
Cuando usas Arrays.<Class<? extends List>>asList(ArrayList.class, LinkedList.class);
Arrays.<Class<? extends List>>asList(ArrayList.class, LinkedList.class);
el tipo ''intersección'' se establece explícitamente (como List
) y no necesita ser inferido por el compilador.
Además de eso, creo que lo que Ted Hopp dijo es correcto para el otro caso.
EDITAR:
La diferencia entre
List<Class<? extends Reference>> list2 = Arrays.asList(WeakReference.class);
y
List<Class<? extends Reference>> list3 = Arrays.<Class<? extends Reference>>asList(WeakReference.class);
puede ser el momento en que el compilador determina el tipo de la nueva lista: supongo que necesita determinar el tipo genérico de la lista antes de considerar la asignación. Para esto toma la información que tiene para inferir el tipo de la nueva lista sin tener en cuenta la asignación. Esto puede ocasionar que dos tipos de listas sean creadas por las dos declaraciones anteriores, lo que resulta en el comportamiento observado.
Hay dos partes en la explicación de este comportamiento:
- ¿Cómo cambia el tipo del lado derecho con los argumentos cambiantes?
- ¿Por qué algunos de los tipos de RHS son incompatibles con el tipo de LHS?
1. El lado derecho
La firma de asList
es
<T> List<T> asList(T... a)
Esto significa que todos los argumentos deben combinarse en un solo tipo T
, que es el tipo más específico común a los tipos de todos los argumentos . En este caso particular, tenemos
asList(WeakReference.class) -> List<Class<WeakReference>>
y
asList(WeakReference.class, SoftReference.class)
-> List<Class<? extends Reference>>
Ambos son bastante obvios.
2. El lado izquierdo
Ahora, ¿por qué no podemos asignar la primera expresión, de tipo List<Class<WeakReference>>
, a una variable de tipo List<Class<? extends Reference>>
List<Class<? extends Reference>>
? La mejor manera de entender por qué las reglas deben serlo es una prueba por contradicción. Considera lo siguiente:
-
List<Class<? extends Reference>>
List<Class<? extends Reference>>
hasadd(Class<? extends Reference>)
-
List<Class<WeakReference>>
tieneadd(Class<WeakReference>)
.
Ahora, si Java le permitió asignar una a la otra:
List<Class<WeakReference>> lw = new ArrayList<>();
List<Class<? extends Reference>> lq = lw;
lq.add(PhantomReference.class);
resultaría en una clara violación de la seguridad del tipo.
Problema interesante. Creo que lo que está pasando es esto. Cuando tiene dos elementos como usted, el tipo de retorno de asList
es el tipo más específico de todos los argumentos, que en su primer ejemplo es List<Reference>
. Esto es compatible con asignación con List<? extends Reference>
List<? extends Reference>
. Cuando tiene un único argumento, el tipo de devolución es el tipo específico del argumento, que no es compatible con la asignación porque los genéricos no son covariantes.