Collections.emptyList () devuelve una Lista <Object>?

269

Tengo problemas para navegar la regla de Java para inferir parámetros de tipo genérico. Considere la siguiente clase, que tiene un parámetro de lista opcional:

import java.util.Collections;
import java.util.List;

public class Person {
  private String name;
  private List<String> nicknames;

  public Person(String name) {
    this(name,Collections.emptyList());
  }

  public Person(String name,List<String> nicknames) {
    this.name = name;
    this.nicknames = nicknames;
  }
}

Mi compilador de Java da el siguiente error:

Person.java:9: The constructor Person(String, List<Object>) is undefined

Pero Collections.emptyList()devuelve el tipo <T> List<T>, no List<Object>. Agregar un reparto no ayuda

public Person(String name) {
  this(name,(List<String>)Collections.emptyList());
}

rendimientos

Person.java:9: inconvertible types

Usando en EMPTY_LISTlugar deemptyList()

public Person(String name) {
  this(name,Collections.EMPTY_LIST);
}

rendimientos

Person.java:9: warning: [unchecked] unchecked conversion

Mientras que el siguiente cambio hace que el error desaparezca:

public Person(String name) {
  this.name = name;
  this.nicknames = Collections.emptyList();
}

¿Alguien puede explicar con qué regla de verificación de tipo me estoy enfrentando aquí y la mejor manera de evitarlo? En este ejemplo, el ejemplo del código final es satisfactorio, pero con clases más grandes, me gustaría poder escribir métodos siguiendo este patrón de "parámetro opcional" sin duplicar el código.

Para crédito adicional: ¿cuándo es apropiado usar EMPTY_LISTen lugar de emptyList()?

Chris Conway
fuente
1
Para todas las preguntas relacionadas con Java Generics, recomiendo " Java Generics and Collections " de Maurice Naftalin, Philip Wadler.
Julien Chastang el

Respuestas:

447

El problema que está encontrando es que a pesar de que el método emptyList()regresa List<T>, no lo ha proporcionado con el tipo, por lo que el valor predeterminado es regresar List<Object>. Puede proporcionar el parámetro de tipo y hacer que su código se comporte como se espera, así:

public Person(String name) {
  this(name,Collections.<String>emptyList());
}

Ahora, cuando realiza una asignación directa, el compilador puede descubrir los parámetros de tipo genérico por usted. Se llama inferencia de tipos. Por ejemplo, si hiciste esto:

public Person(String name) {
  List<String> emptyList = Collections.emptyList();
  this(name, emptyList);
}

entonces la emptyList()llamada devolvería correctamente a List<String>.

InverseFalcon
fuente
12
Entendido. Viniendo del mundo de ML, es extraño para mí que Java no pueda inferir el tipo correcto: el tipo de parámetro formal y el tipo de retorno de emptyList son claramente unificables. Pero supongo que el inferenciador de tipos solo puede dar "pequeños pasos".
Chris Conway
55
En algunos casos simples, puede parecer posible que el compilador deduzca el parámetro de tipo faltante en este caso, pero esto podría ser peligroso. Si existieran varias versiones del método con diferentes parámetros, podría terminar llamando al incorrecto. Y el segundo quizás aún no exista ...
Bill Michell
13
Esa notación "Colecciones. <String> emptyList ()" es realmente extraña, pero tiene sentido. Más fácil que Enum <E extiende Enum <E>>. :)
Thiago Chaves
12
El suministro de un parámetro de tipo ya no es necesario en Java 8 (a menos que haya una ambigüedad en los posibles tipos genéricos).
Vitalii Fedorenko
9
El segundo fragmento muestra muy bien la inferencia de tipos pero, por supuesto, no se compilará. La llamada a thisdebe ser la primera instrucción en el constructor.
Arjan
99

Quieres usar:

Collections.<String>emptyList();

Si observa la fuente de qué emptyList, verá que en realidad solo hace un

return (List<T>)EMPTY_LIST;
Carson
fuente
26

El método emptyList tiene esta firma:

public static final <T> List<T> emptyList()

Que <T>antes de la palabra Lista significa que infiere el valor del parámetro genérico T del tipo de variable al que se asigna el resultado. Entonces en este caso:

List<String> stringList = Collections.emptyList();

El valor de retorno es referenciado explícitamente por una variable de tipo List<String>, para que el compilador pueda resolverlo. En este caso:

setList(Collections.emptyList());

No hay una variable de retorno explícita para que el compilador la use para descubrir el tipo genérico, por lo que su valor predeterminado es Object.

Dan Vinton
fuente