Collections.emptyList () zwraca List <Object>?

269

Mam problem z nawigacją w regule Javy w celu wnioskowania o ogólnych parametrach typu. Rozważ następującą klasę, która ma opcjonalny parametr listy:

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

Mój kompilator Java wyświetla następujący błąd:

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

Ale Collections.emptyList()powraca wpisać <T> List<T>, nie List<Object>. Dodanie obsady nie pomaga

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

daje

Person.java:9: inconvertible types

Używanie EMPTY_LISTzamiastemptyList()

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

daje

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

Podczas gdy następująca zmiana powoduje zniknięcie błędu:

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

Czy ktoś może wyjaśnić, z jaką zasadą sprawdzania typu się tutaj spotykam i jaki jest najlepszy sposób obejścia tego? W tym przykładzie końcowy przykład kodu jest zadowalający, ale przy większych klasach chciałbym móc pisać metody zgodne z tym wzorcem „parametru opcjonalnego” bez powielania kodu.

Dla dodatkowego kredytu: kiedy należy używać, EMPTY_LISTa nie używać emptyList()?

Chris Conway
źródło
1
W przypadku wszystkich pytań związanych z Java Generics gorąco polecam „ Java Generics and Collections ” Maurice'a Naftalina, Philipa Wadlera.
Julien Chastang

Odpowiedzi:

447

Problem, z którym się spotykasz, polega na tym, że chociaż metoda emptyList()powraca List<T>, nie podałeś jej z typem, więc domyślnie jest zwracana List<Object>. Możesz podać parametr type i sprawić, by kod działał zgodnie z oczekiwaniami, w następujący sposób:

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

Teraz, gdy wykonujesz proste przypisanie, kompilator może określić ogólne parametry dla Ciebie. To się nazywa wnioskowanie typu. Na przykład, jeśli to zrobiłeś:

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

wtedy emptyList()połączenie poprawnie zwróci a List<String>.

InverseFalcon
źródło
12
Rozumiem. Pochodząc ze świata ML, dziwne jest dla mnie, że Java nie może wnioskować o poprawnym typie: typ parametru formalnego i typ zwracanej wartości pustej listy są jednoznaczne. Ale myślę, że inferencja typu może podejmować tylko „kroki dziecka”.
Chris Conway,
5
W niektórych prostych przypadkach może się wydawać, że w tym przypadku kompilator może wnioskować o brakującym parametrze typu - ale może to być niebezpieczne. Jeśli istniało wiele wersji metody z różnymi parametrami, możesz w końcu wywołać niewłaściwy. A drugi może jeszcze nie istnieć ...
Bill Michell,
13
Ta notacja „Kolekcje. <String> emptyList ()” jest naprawdę dziwna, ale ma sens. Łatwiej niż Enum <E rozszerza Enum <E>>. :)
Thiago Chaves
12
Podanie parametru typu nie jest już wymagane w Javie 8 (chyba że istnieje dwuznaczność w możliwych typach ogólnych).
Vitalii Fedorenko
9
Drugi fragment kodu ładnie pokazuje wnioskowanie typu, ale oczywiście nie można go skompilować. Wywołanie thismusi być pierwszą instrukcją w konstruktorze.
Arjan,
99

Chcesz użyć:

Collections.<String>emptyList();

Jeśli spojrzysz na źródło tego, co pusta lista widzi, że tak naprawdę to po prostu

return (List<T>)EMPTY_LIST;
Carson
źródło
26

Metoda emptyList ma następujący podpis:

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

To, że <T>przed słowem Lista oznacza, że ​​oblicza wartość parametru ogólnego T z typu zmiennej, do której przypisany jest wynik. Więc w tym przypadku:

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

Zwracana wartość jest następnie jawnie przywoływana przez zmienną typu List<String>, dzięki czemu kompilator może ją rozgryźć. W tym przypadku:

setList(Collections.emptyList());

Kompilator nie ma wyraźnej zmiennej zwrotnej, której mógłby użyć do określenia typu ogólnego, więc domyślnie jest to Object.

Dan Vinton
źródło