Collections.emptyList () renvoie un List <Object>?

269

J'ai du mal à naviguer dans la règle de Java pour déduire des paramètres de type génériques. Considérez la classe suivante, qui a un paramètre de liste facultatif:

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

Mon compilateur Java donne l'erreur suivante:

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

Mais Collections.emptyList()renvoie le type <T> List<T>, non List<Object>. L'ajout d'un casting n'aide pas

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

les rendements

Person.java:9: inconvertible types

Utiliser EMPTY_LISTau lieu deemptyList()

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

les rendements

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

Alors que la modification suivante fait disparaître l'erreur:

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

Quelqu'un peut-il expliquer à quelle règle de vérification de type je me heurte ici et quelle est la meilleure façon de contourner ce problème? Dans cet exemple, l'exemple de code final est satisfaisant, mais avec des classes plus grandes, j'aimerais pouvoir écrire des méthodes suivant ce modèle de "paramètre facultatif" sans dupliquer le code.

Pour un crédit supplémentaire: quand est-il approprié d'utiliser EMPTY_LISTpar opposition à emptyList()?

Chris Conway
la source
1
Pour toutes les questions liées à Java Generics, je recommande fortement " Java Generics and Collections " de Maurice Naftalin, Philip Wadler.
Julien Chastang

Réponses:

447

Le problème que vous rencontrez est que même si la méthode emptyList()retourne List<T>, vous ne lui avez pas fourni le type, donc elle revient par défaut List<Object>. Vous pouvez fournir le paramètre type et faire en sorte que votre code se comporte comme prévu, comme ceci:

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

Maintenant, lorsque vous effectuez une affectation directe, le compilateur peut déterminer les paramètres de type générique pour vous. Cela s'appelle l'inférence de type. Par exemple, si vous avez fait ceci:

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

alors l' emptyList()appel renverrait correctement a List<String>.

InverseFalcon
la source
12
Je l'ai. Venant du monde ML, il est étrange pour moi que Java ne puisse pas déduire le type correct: le type de paramètre formel et le type de retour de emptyList sont clairement unifiables. Mais je suppose que l'inférenceur de type ne peut faire que des "petits pas".
Chris Conway
5
Dans certains cas simples, il peut sembler possible pour le compilateur de déduire le paramètre de type manquant dans ce cas - mais cela peut être dangereux. Si plusieurs versions de la méthode existaient avec des paramètres différents, vous pourriez finir par appeler la mauvaise. Et le second n'existe peut-être même pas encore ...
Bill Michell
13
Cette notation "Collections. <String> emptyList ()" est vraiment étrange, mais a du sens. Plus simple qu'Enum <E étend Enum <E>>. :)
Thiago Chaves
12
La fourniture d'un paramètre de type n'est plus requise dans Java 8 (sauf s'il existe une ambiguïté dans les types génériques possibles).
Vitalii Fedorenko
9
Le deuxième extrait montre bien l'inférence de type mais ne compile pas, bien sûr. L'appel à thisdoit être la première instruction du constructeur.
Arjan
99

Vous souhaitez utiliser:

Collections.<String>emptyList();

Si vous regardez la source de ce que emptyList voyez-vous qu'il fait en fait juste un

return (List<T>)EMPTY_LIST;
carson
la source
26

la méthode emptyList a cette signature:

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

Ce qui <T>précède le mot Liste signifie qu'il déduit la valeur du paramètre générique T du type de variable auquel le résultat est affecté. Donc dans ce cas:

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

La valeur de retour est ensuite référencée explicitement par une variable de type List<String>, afin que le compilateur puisse la comprendre. Dans ce cas:

setList(Collections.emptyList());

Il n'y a pas de variable de retour explicite à utiliser par le compilateur pour déterminer le type générique, il est donc par défaut Object.

Dan Vinton
la source