Collections.emptyList () возвращает List <Object>?

У меня проблемы с навигацией по правилу 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()?

По всем вопросам, связанным с Java Generics, я настоятельно рекомендую "Обобщения и коллекции Java" Мориса Нафталина, Филипа Вадлера.

Julien Chastang 20.11.2008 23:39
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
274
1
213 187
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

Collections.<String>emptyList();

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

return (List<T>)EMPTY_LIST;

у метода emptyList есть эта подпись:

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

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

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

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

setList(Collections.emptyList());

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

Ответ принят как подходящий

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

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

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

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

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

Понятно. Исходя из мира машинного обучения, мне кажется странным, что Java не может определить правильный тип: тип формального параметра и тип возвращаемого значения emptyList явно унифицированы. Но я предполагаю, что логический вывод типов может делать только «маленькие шажки».

Chris Conway 20.11.2008 23:43

В некоторых простых случаях компилятору может показаться возможным определить отсутствующий параметр типа в этом случае, но это может быть опасно. Если существует несколько версий метода с разными параметрами, вы можете вызвать неправильную версию. А второго может и не быть ...

Bill Michell 25.11.2008 16:47

Такое обозначение «Коллекции. <String> emptyList ()» действительно странно, но имеет смысл. Проще, чем Enum <E extends Enum <E>>. :)

Thiago Chaves 23.06.2009 21:07

В Java 8 больше не требуется указывать параметр типа (если нет двусмысленности в возможных универсальных типах).

Vitalii Fedorenko 07.04.2014 00:10

Второй фрагмент хорошо показывает вывод типа, но, конечно, не компилируется. Вызов this должен быть первым оператором в конструкторе.

Arjan 10.07.2016 05:30

@ChrisConway Существует аргумент «программной инженерии» против умных выводов типов, особенно тех, которые работают за пределами методов или модулей. Тип - это часть интерфейса, и если вы не записываете его явно, он может непредсказуемо дрейфовать со временем. Вы не можете написать предполагаемые типы (они, как правило, недружелюбны по отношению к человеку, иногда даже не могут быть выражены на языке, они являются «максимальными» в смысле «все это будет работать», а не «минимальными» в человекоцентричном »this это то, что мы создаем, с чем мы будем тестировать, о чем мы заботимся ".)

user7610 25.02.2019 21:14

Начиная с Java 8, этот вид кода компилируется, как ожидалось, и параметр типа определяется компилятором.

public Person(String name) {
    this(name, Collections.emptyList()); // Inferred to List<String> in Java 8
}

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

Новым в Java 8 является то, что тип цели выражения будет использоваться для вывода параметров типа его подвыражений. До Java 8 только прямые присваивания и аргументы методам, которые использовались для вывода параметров типа.

В этом случае тип параметра конструктора будет целевым типом для Collections.emptyList(), а тип возвращаемого значения будет выбран в соответствии с типом параметра.

Этот механизм был добавлен в Java 8 в основном для возможности компиляции лямбда-выражений, но в целом он улучшает вывод типов.

С каждым выпуском Java приближается к правильному выводу типа Хиндли-Милнер!

Другие вопросы по теме