Collections.emptyList () возвращает список <Object>?

269

У меня возникли некоторые проблемы при навигации по правилу Java для определения параметров универсального типа. Рассмотрим следующий класс, который имеет необязательный параметр списка:

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

Мой компилятор Java выдает следующую ошибку:

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

Но Collections.emptyList()возвращает тип <T> List<T>, а не List<Object>. Добавление актеров не помогает

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

доходность

Person.java:9: inconvertible types

Использование EMPTY_LISTвместоemptyList()

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

доходность

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

Принимая во внимание, что следующее изменение устраняет ошибку:

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

Может кто-нибудь объяснить, с каким правилом проверки типов я здесь сталкиваюсь, и как лучше обойти это? В этом примере последний пример кода является удовлетворительным, но с более крупными классами я хотел бы иметь возможность писать методы, следуя этому шаблону «необязательного параметра», без дублирования кода.

Для дополнительного кредита: когда уместно использовать EMPTY_LISTв отличие от emptyList()?

Крис Конвей
источник
1
Для всех вопросов, связанных с Java Generics, я настоятельно рекомендую Мериса Нафталина, Филипа Уодлера, « Generics и Коллекции Java ».
Жюльен Частанг

Ответы:

447

Проблема, с которой вы сталкиваетесь, заключается в том, что, несмотря на то, что метод emptyList()возвращается List<T>, вы не предоставили ему тип, поэтому по умолчанию он возвращается List<Object>. Вы можете предоставить параметр type, и ваш код будет работать так, как ожидается:

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

Теперь, когда вы делаете прямое присваивание, компилятор может определить параметры обобщенного типа для вас. Это называется вывод типа. Например, если вы сделали это:

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

тогда emptyList()вызов правильно вернул бы List<String>.

InverseFalcon
источник
12
Понял. Исходя из мира ML, мне странно, что Java не может определить правильный тип: тип формального параметра и тип возвращаемого значения emptyList явно различимы. Но я полагаю, что логический тип может делать только "шаги ребенка".
Крис Конвей
5
В некоторых простых случаях может показаться, что компилятор может определить отсутствующий параметр типа в этом случае, но это может быть опасно. Если существует несколько версий метода с разными параметрами, вы можете в итоге вызвать неправильную. А второй может даже не существовать ...
Билл Мичелл
13
Эта запись «Коллекции. <String> emptyList ()» действительно странная, но имеет смысл. Проще, чем Enum <E расширяет Enum <E >>. :)
Тьяго Чавес
12
В Java 8 больше не требуется указывать параметр типа (если не существует неоднозначности в возможных универсальных типах).
Виталий Федоренко
9
Второй фрагмент кода показывает вывод типа, но не компилируется, конечно. Вызов thisдолжен быть первым оператором в конструкторе.
Арджан
99

Вы хотите использовать:

Collections.<String>emptyList();

Если вы посмотрите на источник для того, что emptyList вы видите, что он на самом деле просто делает

return (List<T>)EMPTY_LIST;
Carson
источник
26

метод emptyList имеет следующую подпись:

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

То, что <T>перед словом List означает, что оно выводит значение универсального параметра T из типа переменной, которой присвоен результат. Итак, в этом случае:

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

Затем на возвращаемое значение явно ссылается переменная типа List<String>, так что компилятор может это выяснить. В таком случае:

setList(Collections.emptyList());

Для компилятора не существует явной возвращаемой переменной для определения универсального типа, поэтому по умолчанию используется значение Object.

Дэн Винтон
источник