Позвольте мне сделать это время история удлиненная кровать; прочитать его засыпает :)
Давайте начнем с этой точкой - Чтобы вызвать универсальный метод, его аргументы типа должны быть поставлены. (Если метод не вызван «необработанным» способом, то есть в стираемой форме, что является другим темой):
Например, для вызова Collections.<T>emptyList()
необходимо указать T
. Он может быть поставлен явно программистом -
List<String> list = Collections.<String>emptyList(); // T=String
Но это утомительно, и вид немой. Очевидно, что в этом контексте T
может быть только String
. Глупо, если программист должен повторить очевидное.
Здесь Тип вывода полезен.Мы можем опустить аргумент типа, и компилятор может сделать вывод, что программист намеревается это будет
List<String> list = Collections.emptyList(); // T=String is implied
Помните, <String>
все еще подается, программист, неявно.
Возможно, программист всезнающий диктатор всех аргументов типа, и компилятор и программист есть общее понимание о том, когда аргументы типа могут быть опущены и выводимый из контекста. Когда программист опускает аргумент типа, он знает, что компилятор может вывести его точно так, как он предполагал, на основе строгого алгоритма (который он мастерит :) Это не усмотрение компилятора выбрать и выбрать аргументы типа, скорее, программист делает , и передает его компилятору.
Реалистично, умозаключение типа является настолько сложным, несколько не программист не имеет ни малейшего представления, что происходит в большинстве случаев :) Программатор больше как диктатор делает неопределенные команды, а компилятор пытается все возможное, чтобы иметь смысл из него. Мы в основном пишем код по интуиции, не обращая внимания на детали, и мы считаем, что код делает то, что мы хотим, если компилятор его одобрит.
В любом случае все аргументы типа фиксируются точно и предсказуемо во время компиляции. Любой аргумент пропущенного типа эквивалентен явно заданному.
Некоторые аргументы типа являются «неопровержимыми», например. переменная типа, введенная путем преобразования захвата. Они не могут быть явно указаны, они могут быть только выведены. (Тем не менее, программист должен знать, что они, несмотря на то, что они не могут быть названы)
В предыдущем примере, T
можно сделать вывод только String
, нет никаких других вариантов. Но во многих случаях есть больше кандидатов для T
, и алгоритм вывода типа должен иметь стратегию для его решения одному из кандидатов. Например, рассмотрим это одинокое заявление
Collections.emptyList();
T
может быть любого типа; T
разрешен к Object
, потому что, ну, нет веских оснований разрешать его на что-либо еще, например Integer
или String
и т. Д. Object
более особенный, потому что это супертип всего.
Теперь давайте перейдем к конструкторам. Формально говоря, конструкторы не являются методами. Но они очень похожи во многих аспектах. В частности, вывод типа для конструкторов почти такой же, как и для методов. Вызов конструктора класса CLASS имеет вид new CLASS(args)
.
Подобно методам, конструктор может быть общим, с его собственными параметрами типа.Например,
class Bar
{
<T>Bar(T x){ .. }
и вывода типа работает на общих конструкторах тоже
new Bar("abc"); // inferred: T=String
Чтобы явно задать аргументы типа для конструктора,
new <String>Bar("abc");
Это довольно редко, хотя, что конструктор является общим ,
Генеральный конструктор отличается от общего класса! Рассмотрим это
class Foo<T>
{
Foo(T x){ .. }
Класс является общим, конструктор - нет. Чтобы вызвать конструктор класса Foo<String>
, мы делаем
new Foo<String>(""); // CLASS = Foo<String>
типа Метода вывод что мы говорим о до сих пор не применяется здесь, потому что конструктор даже не родовой. В Java 5/6 для CLASS нет вывода типа, поэтому должно быть явно указано <String>
. Это глупо, потому что <String>
очевидно в этом контексте. Были обходные пути (т. Е. Использование статических заводских методов), но люди, конечно, очень расстроились и потребовали решения.
В Java 7, эта проблема решается с помощью "алмаза умозаключения" -
new Foo<>(""); // inferred: T=String
"алмазных" относится к любопытному <>
оператору. Требуется; мы не можем просто написать
new Foo("");
потому, что уже имели иной смысл - вызывая конструктор «сырой» Foo
.
С умозаключениями алмазов, мы можем сделать вещи, которые мы не могли бы в Java 5/6
List<Object> list = new ArrayList<>(); // Java 7. inferred: E=Object
// equivalent to
List<Object> list = new ArrayList<Object>(); // <Object> is required in Java 5/6
Помните, что T=Object
все еще подаются через умозаключение алмазов.
Наконец, мы возвращаемся к первоначальному вопросу
List<?> list = new ArrayList<>();
Здесь E=Object
выводится (что еще?). Код эквивалентен
List<?> list = new ArrayList<Object>();
Yep, то list
объект действительно является ArrayList<Object>
, не ArrayList<SomethingElse>
.
Также обратите внимание, что следующий будет незаконным и бессмысленные
List<?> list = new ArrayList<?>();
^^^
CLASS
в new CLASS(args)
должен быть конкретный тип. Мы можем создать экземпляр ArrayList
определенного типа элемента.
Заявленный тип List<?>
переменной list
является слишком общим, хотя. Для локальной переменной, это лучшая практика ИМО объявить его в более конкретном типе
ArrayList<Object> list = new ArrayList<>();
Не используйте <?>
здесь - это просто вызывает путаницу всем.
На соответствующую записку, много людей будет выступать за «программу против интерфейса»
List<Object> list = new ArrayList<>();
^^^^
Это неправильно IMO. Кому мы предоставляем абстракцию в локальном блоке? Используйте наиболее специфичный тип реализации для максимальной ясности; использовать абстрактные типы в интерфейсах.
zzzzzzzzzz
можно вставить только 'null' в' 'Список >, в соответствии с [Java Учебник по неограниченными Wildcards] (https://docs.oracle.com/javase/tutorial/ java/generics/unboundedWildcards.html) –