Статьи

Полный курс по языку Java — от установки JDK до многопоточности и HTTP-запросов

Generics

Страница 21 из 30 · 2026-10-25 · 5 мин чтения

Зачем нужны generics

Без обобщений коллекция хранит Object, и ошибки типов обнаруживаются только во время выполнения:

import java.util.ArrayList;import java.util.List;public class WithoutGenerics {    public static void main(String[] args) {        List list = new ArrayList();        list.add("строка");        list.add(42);        String s = (String) list.get(1);    }}

Последняя строка бросит ClassCastException. Обобщения переносят проверку на этап компиляции:

import java.util.ArrayList;import java.util.List;public class WithGenerics {    public static void main(String[] args) {        List<String> list = new ArrayList<>();        list.add("строка");        String s = list.get(0);    }}

Добавить в List<String> число компилятор не позволит.

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

public class Box<T> {    private T value;    public Box(T value) {        this.value = value;    }    public T get() {        return value;    }    public void set(T value) {        this.value = value;    }}
public class Main {    public static void main(String[] args) {        Box<String> text = new Box<>("привет");        Box<Integer> number = new Box<>(42);        String s = text.get();        int n = number.get();        System.out.println(s + " " + n);    }}

Параметр типа нельзя заменить примитивом: Box<int> недопустим, используйте Box<Integer>.

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

public class Pair<A, B> {    private final A first;    private final B second;    public Pair(A first, B second) {        this.first = first;        this.second = second;    }    public A getFirst() {        return first;    }    public B getSecond() {        return second;    }    public Pair<B, A> swap() {        return new Pair<>(second, first);    }    @Override    public String toString() {        return "(" + first + ", " + second + ")";    }    public static void main(String[] args) {        Pair<String, Integer> pair = new Pair<>("возраст", 30);        System.out.println(pair);        System.out.println(pair.swap());    }}

Принятые имена параметров: T — type, E — element, K — key, V — value, R — result.

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

import java.util.List;public class Utils {    public static <T> T first(List<T> list) {        if (list.isEmpty()) {            throw new IllegalArgumentException("Список пуст");        }        return list.get(0);    }    public static <T> void swap(T[] array, int i, int j) {        T temp = array[i];        array[i] = array[j];        array[j] = temp;    }    public static void main(String[] args) {        System.out.println(first(List.of("a", "b")));        System.out.println(first(List.of(1, 2, 3)));        String[] words = {"один", "два", "три"};        swap(words, 0, 2);        System.out.println(String.join(", ", words));    }}

Компилятор выводит тип автоматически.

Ограничения типов

extends задает верхнюю границу:

import java.util.List;public class Bounds {    public static <T extends Comparable<T>> T max(List<T> list) {        T best = list.get(0);        for (T item : list) {            if (item.compareTo(best) > 0) {                best = item;            }        }        return best;    }    public static <T extends Number> double sum(List<T> numbers) {        double total = 0;        for (T n : numbers) {            total += n.doubleValue();        }        return total;    }    public static void main(String[] args) {        System.out.println(max(List.of(3, 9, 2)));        System.out.println(max(List.of("яблоко", "груша", "слива")));        System.out.println(sum(List.of(1, 2, 3)));        System.out.println(sum(List.of(1.5, 2.5)));    }}

Несколько границ: <T extends Number & Comparable<T>>.

Wildcard

Знак ? обозначает неизвестный тип.

import java.util.ArrayList;import java.util.List;public class Wildcards {    static void printAll(List<?> list) {        for (Object o : list) {            System.out.println(o);        }    }    static double sum(List<? extends Number> list) {        double total = 0;        for (Number n : list) {            total += n.doubleValue();        }        return total;    }    static void fill(List<? super Integer> list) {        for (int i = 0; i < 3; i++) {            list.add(i);        }    }    public static void main(String[] args) {        printAll(List.of("a", 1, 2.5));        System.out.println(sum(List.of(1, 2, 3)));        List<Number> numbers = new ArrayList<>();        fill(numbers);        System.out.println(numbers);    }}

Правило PECS: Producer Extends, Consumer Super.

  • из списка ? extends T можно только читать;
  • в список ? super T можно только писать.

Инвариантность

List<Integer> не является подтипом List<Number>, хотя Integer — подтип Number. Иначе в список целых можно было бы положить Double. Для гибкости используйте wildcard.

Стирание типов

После компиляции информация об обобщенных параметрах удаляется. Отсюда ограничения:

  • нельзя создать new T() и new T[10];
  • нельзя проверить instanceof List<String>;
  • нельзя перегрузить методы, отличающиеся только параметром типа;
  • нельзя создавать статические поля типа T.
import java.util.ArrayList;import java.util.List;public class Erasure {    public static void main(String[] args) {        List<String> strings = new ArrayList<>();        List<Integer> numbers = new ArrayList<>();        System.out.println(strings.getClass() == numbers.getClass());    }}

Вывод: true.

Обобщенные интерфейсы

public interface Repository<T, ID> {    void save(T item);    T findById(ID id);}
import java.util.HashMap;import java.util.Map;public class UserRepository implements Repository<User, Integer> {    private final Map<Integer, User> storage = new HashMap<>();    @Override    public void save(User user) {        storage.put(user.id(), user);    }    @Override    public User findById(Integer id) {        return storage.get(id);    }}
public record User(int id, String name) {}

Пример: собственный стек

import java.util.ArrayList;import java.util.List;public class Stack<T> {    private final List<T> items = new ArrayList<>();    public void push(T item) {        items.add(item);    }    public T pop() {        if (items.isEmpty()) {            throw new IllegalStateException("Стек пуст");        }        return items.remove(items.size() - 1);    }    public T peek() {        if (items.isEmpty()) {            throw new IllegalStateException("Стек пуст");        }        return items.get(items.size() - 1);    }    public boolean isEmpty() {        return items.isEmpty();    }    public int size() {        return items.size();    }    public static void main(String[] args) {        Stack<String> stack = new Stack<>();        stack.push("a");        stack.push("b");        System.out.println(stack.pop());        System.out.println(stack.peek());    }}