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




Вы хотите использовать:
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 явно унифицированы. Но я предполагаю, что логический вывод типов может делать только «маленькие шажки».
В некоторых простых случаях компилятору может показаться возможным определить отсутствующий параметр типа в этом случае, но это может быть опасно. Если существует несколько версий метода с разными параметрами, вы можете вызвать неправильную версию. А второго может и не быть ...
Такое обозначение «Коллекции. <String> emptyList ()» действительно странно, но имеет смысл. Проще, чем Enum <E extends Enum <E>>. :)
В Java 8 больше не требуется указывать параметр типа (если нет двусмысленности в возможных универсальных типах).
Второй фрагмент хорошо показывает вывод типа, но, конечно, не компилируется. Вызов this должен быть первым оператором в конструкторе.
@ChrisConway Существует аргумент «программной инженерии» против умных выводов типов, особенно тех, которые работают за пределами методов или модулей. Тип - это часть интерфейса, и если вы не записываете его явно, он может непредсказуемо дрейфовать со временем. Вы не можете написать предполагаемые типы (они, как правило, недружелюбны по отношению к человеку, иногда даже не могут быть выражены на языке, они являются «максимальными» в смысле «все это будет работать», а не «минимальными» в человекоцентричном »this это то, что мы создаем, с чем мы будем тестировать, о чем мы заботимся ".)
Начиная с 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 приближается к правильному выводу типа Хиндли-Милнер!
По всем вопросам, связанным с Java Generics, я настоятельно рекомендую "Обобщения и коллекции Java" Мориса Нафталина, Филипа Вадлера.