Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Обобщения (generics)

Вы уже писали List<String> и Map<String, Integer>. Угловые скобки — это обобщения (generics): они позволяют написать класс или метод один раз и использовать его с разными типами, сохраняя проверку типов компилятором.

Зачем нужны обобщения

Представим коробку, в которой может лежать что угодно. Без обобщений её поле пришлось бы сделать типа Object, а при чтении приводить тип вручную — и ошибка обнаружится только при запуске:

class Box {
    Object value;
}
 
void main() {
    Box box = new Box();
    box.value = "Привет";
    Integer number = (Integer) box.value;
}

Результат:

Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')

Обобщённый класс

Добавим к классу параметр типа T — имя, вместо которого при использовании подставят настоящий тип. Теперь компилятор знает, что лежит в коробке, и приведение не нужно:

class Box<T> {
    private T value;
 
    void put(T value) {
        this.value = value;
    }
 
    T get() {
        return value;
    }
}
 
void main() {
    Box<String> box = new Box<>();
    box.put("Привет");
    String text = box.get();
    IO.println(text.toUpperCase());
}

Результат:

ПРИВЕТ

Попытка положить в Box<String> число теперь не скомпилируется — ошибка видна сразу, а не у пользователя.

Несколько параметров

Параметров типа может быть несколько. По соглашению их называют одной заглавной буквой: T — тип, K и V — ключ и значение, E — элемент. Записи тоже бывают обобщёнными:

record Pair<A, B>(A first, B second) {}
 
void main() {
    Pair<String, Integer> score = new Pair<>("Анна", 90);
    IO.println(score.first() + ": " + score.second());
    IO.println(score);
}

Результат:

Анна: 90
Pair[first=Анна, second=90]

Обобщённый метод

Параметр типа можно объявить и у отдельного метода — перед типом результата. При вызове Java сама выводит T из аргументов:

<T> T last(List<T> items) {
    return items.get(items.size() - 1);
}
 
void main() {
    String name = last(List.of("Анна", "Олег"));
    Integer number = last(List.of(1, 2, 3));
    IO.println(name);
    IO.println(number);
}

Результат:

Олег
3

Один метод работает со списком строк, чисел и чего угодно ещё, и результат каждый раз имеет правильный тип.

Где ещё встречаются обобщения

Почти вся стандартная библиотека обобщённая: коллекции, Optional, Comparator, функциональные интерфейсы для лямбд. Когда вы пишете Comparator<Product>, вы подставляете тип в параметр T интерфейса Comparator<T>. Поэтому читать запись вида Map<K, V> в документации стоит уметь с самого начала.

Частая ошибка

В параметр типа можно подставить только ссылочный тип — класс, запись, интерфейс. Примитивы вроде int и double не подходят:

void main() {
    List<int> numbers = new ArrayList<>();
}

Результат:

Main.java:2: error: unexpected type
    List<int> numbers = new ArrayList<>();
         ^
  required: reference
  found:    int

Используйте классы-обёртки: List<Integer>, Map<String, Double>. Java сама упакует числа в объекты и распакует обратно.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.