Collections.emptyList () gibt eine Liste <Objekt> zurück?

269

Ich habe Probleme beim Navigieren in der Java-Regel zum Ableiten generischer Typparameter. Betrachten Sie die folgende Klasse mit einem optionalen Listenparameter:

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

Mein Java-Compiler gibt den folgenden Fehler aus:

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

Aber Collections.emptyList()kehren geben <T> List<T>, nicht List<Object>. Das Hinzufügen einer Besetzung hilft nicht

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

ergibt

Person.java:9: inconvertible types

Verwenden EMPTY_LISTstattemptyList()

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

ergibt

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

Durch die folgende Änderung wird der Fehler behoben:

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

Kann jemand erklären, gegen welche Regel zur Typprüfung ich hier stoße und wie ich sie am besten umgehen kann? In diesem Beispiel ist das endgültige Codebeispiel zufriedenstellend, aber bei größeren Klassen möchte ich in der Lage sein, Methoden zu schreiben, die diesem Muster "optionaler Parameter" folgen, ohne Code zu duplizieren.

Für zusätzliche Gutschrift: Wann ist es angebracht, EMPTY_LISTim Gegensatz zu zu verwenden emptyList()?

Chris Conway
quelle
1
Für alle Fragen zu Java Generics empfehle ich " Java Generics and Collections " von Maurice Naftalin, Philip Wadler.
Julien Chastang

Antworten:

447

Das Problem , das Sie sind Begegnungs ist , dass auch wenn die Methode emptyList()zurückkehrt List<T>, haben Sie nicht mit der Art vorausgesetzt , es ist , so wird standardmäßig auf die Rückkehr List<Object>. Sie können den Typparameter angeben und Ihren Code wie erwartet verhalten lassen:

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

Wenn Sie jetzt eine direkte Zuweisung vornehmen, kann der Compiler die generischen Typparameter für Sie ermitteln. Es heißt Typinferenz. Wenn Sie dies beispielsweise getan haben:

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

dann würde der emptyList()Anruf korrekt a zurückgeben List<String>.

InverseFalcon
quelle
12
Verstanden. Aus der ML-Welt kommend, ist es für mich seltsam, dass Java nicht auf den richtigen Typ schließen kann: Der Typ des formalen Parameters und der Rückgabetyp von emptyList sind eindeutig nicht identifizierbar. Aber ich denke, der Typ-Inferencer kann nur "kleine Schritte" machen.
Chris Conway
5
In einigen einfachen Fällen scheint es dem Compiler in diesem Fall möglich zu sein, auf den fehlenden Typparameter zu schließen - dies kann jedoch gefährlich sein. Wenn mehrere Versionen der Methode mit unterschiedlichen Parametern vorhanden waren, wird möglicherweise die falsche aufgerufen. Und der zweite könnte noch nicht einmal existieren ...
Bill Michell
13
Diese Notation "Sammlungen. <String> emptyList ()" ist wirklich seltsam, macht aber Sinn. Einfacher als Enum <E erweitert Enum <E>>. :)
Thiago Chaves
12
Die Angabe eines Typparameters ist in Java 8 nicht mehr erforderlich (es sei denn, mögliche generische Typen sind nicht eindeutig).
Vitalii Fedorenko
9
Das zweite Snippet zeigt zwar eine gute Typinferenz, wird aber natürlich nicht kompiliert. Der Aufruf von thismuss die erste Anweisung im Konstruktor sein.
Arjan
99

Sie möchten verwenden:

Collections.<String>emptyList();

Wenn Sie sich die Quelle für die leere Liste ansehen, sehen Sie, dass sie tatsächlich nur a ausführt

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

Die Methode emptyList hat folgende Signatur:

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

Das <T>bedeutet, dass vor dem Wort Liste der Wert des generischen Parameters T aus dem Variablentyp abgeleitet wird, dem das Ergebnis zugewiesen ist. Also in diesem Fall:

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

Der Rückgabewert wird dann explizit von einer Variablen vom Typ referenziert List<String>, damit der Compiler ihn herausfinden kann. In diesem Fall:

setList(Collections.emptyList());

Es gibt keine explizite Rückgabevariable, mit der der Compiler den generischen Typ ermitteln kann. Der Standardwert ist daher Object.

Dan Vinton
quelle