«1. Введение

Определение типов было введено в Java 5 в дополнение к обобщениям и было существенно расширено в следующих выпусках Java, что также называется Обобщенным выводом целевого типа.

В этом руководстве мы рассмотрим эту концепцию с примерами кода.

2. Универсальные шаблоны

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

Однако введение дженериков привело к необходимости написания стандартного кода из-за необходимости передавать параметры типа. Некоторые примеры:

Map<String, Map<String, String>> mapOfMaps = new HashMap<String, Map<String, String>>();
List<String> strList = Collections.<String>emptyList();
List<Integer> intList = Collections.<Integer>emptyList();

3. Вывод типов До Java 8

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

Теперь мы можем вызывать те же универсальные типы и методы без указания типов параметров. Компилятор автоматически определяет типы параметров, когда это необходимо.

Мы можем видеть тот же код, используя новую концепцию:

List<String> strListInferred = Collections.emptyList();
List<Integer> intListInferred = Collections.emptyList();

В приведенном выше примере на основе ожидаемых возвращаемых типов List\u003cString\u003e и List\u003cInteger\u003e компилятор может вывести параметр типа к следующему универсальному методу:

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

Как мы видим, результирующий код является кратким. Теперь мы можем вызывать универсальные методы как обычные методы, если можно вывести параметр типа.

В Java 5 мы могли бы делать вывод о типах в определенных контекстах, как показано выше.

Java 7 расширил контексты, в которых это могло выполняться. Он представил алмазный оператор \u003c\u003e. Вы можете прочитать больше об операторе бриллиантов в этой статье.

Теперь мы можем выполнить эту операцию для конструкторов универсальных классов в контексте присваивания. Один из таких примеров:

Map<String, Map<String, String>> mapOfMapsInferred = new HashMap<>();

Здесь компилятор Java использует ожидаемый тип присваивания для вывода параметров типа в конструктор HashMap.

4. Обобщенный вывод целевого типа — Java 8

Java 8 еще больше расширил возможности вывода типа. Мы называем эту расширенную возможность вывода обобщенным выводом целевого типа. Вы можете прочитать технические подробности здесь.

В Java 8 также появились лямбда-выражения. Лямбда-выражения не имеют явного типа. Их тип выводится, глядя на целевой тип контекста или ситуации. Target-Type выражения — это тип данных, который компилятор Java ожидает в зависимости от того, где появляется выражение.

Java 8 поддерживает вывод с использованием Target-Type в контексте метода. Когда мы вызываем общий метод без явных аргументов типа, компилятор может просмотреть вызов метода и соответствующие объявления метода, чтобы определить аргумент типа (или аргументы), которые делают вызов применимым.

Давайте посмотрим на пример кода:

static <T> List<T> add(List<T> list, T a, T b) {
    list.add(a);
    list.add(b);
    return list;
}

List<String> strListGeneralized = add(new ArrayList<>(), "abc", "def");
List<Integer> intListGeneralized = add(new ArrayList<>(), 1, 2);
List<Number> numListGeneralized = add(new ArrayList<>(), 1, 2.0);

В коде ArrayList\u003c\u003e не предоставляет явно аргумент типа. Таким образом, компилятор должен вывести это. Сначала компилятор просматривает аргументы метода add. Затем он просматривает параметры, переданные при различных вызовах.

Он выполняет анализ логического вывода о применимости вызова, чтобы определить, применим ли метод к этим вызовам. Если из-за перегрузки применимо несколько методов, компилятор выберет наиболее конкретный метод.

Затем компилятор выполняет анализ вывода типа вызова, чтобы определить аргументы типа. Ожидаемые типы целей также используются в этом анализе. Он выводит аргументы в трех экземплярах как ArrayList\u003cString\u003e, ArrayList\u003cInteger\u003e и ArrayList\u003cNumber\u003e.

Вывод целевого типа позволяет нам не указывать типы для параметров лямбда-выражения:

List<Integer> intList = Arrays.asList(5, 2, 4, 2, 1);
Collections.sort(intList, (a, b) -> a.compareTo(b));

List<String> strList = Arrays.asList("Red", "Blue", "Green");
Collections.sort(strList, (a, b) -> a.compareTo(b));

Здесь параметры a и b не имеют явно определенных типов. Их типы выводятся как Integer в первом лямбда-выражении и как String во втором.

5. Заключение

В этой быстрой статье мы рассмотрели вывод типов, который вместе с дженериками и лямбда-выражением позволяет нам писать краткий код Java.

«Как обычно, полный исходный код можно найти на Github.