← Назад к вопросам

Что такое дженерики?

1.3 Junior🔥 201 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Дженерики (Generics) в Java

Дженерики — это механизм, позволяющий создавать классы, интерфейсы и методы, которые работают с типизированными параметрами. Благодаря дженерикам код становится более безопасным, переиспользуемым и читаемым.

Основная идея

Дженерики позволяют определить тип данных при создании или использовании класса:

// Без дженериков (старый способ)
List list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0); // нужна явная типизация

// С дженериками (современный способ)
List<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0); // типизация уже известна

1. Дженерик класс

// T — это параметр типа (type parameter)
public class Container<T> {
    private T value;
    
    public void setValue(T value) {
        this.value = value;
    }
    
    public T getValue() {
        return value;
    }
}

// Использование
Container<String> stringContainer = new Container<>();
stringContainer.setValue("Hello");
String result = stringContainer.getValue(); // String, без типизации

Container<Integer> intContainer = new Container<>();
intContainer.setValue(42);
Integer intResult = intContainer.getValue(); // Integer

2. Дженерик метод

public class Util {
    // Дженерик метод
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.println(element);
        }
    }
    
    // Дженерик метод с ограничением типа
    public static <T extends Number> double sum(T[] numbers) {
        double total = 0;
        for (T number : numbers) {
            total += number.doubleValue();
        }
        return total;
    }
}

// Использование
String[] strings = {"A", "B", "C"};
Util.printArray(strings);

Integer[] integers = {1, 2, 3};
Util.printArray(integers);
System.out.println(Util.sum(integers)); // 6.0

3. Ограничения типов (Bounds)

Верхняя граница (extends)

// T может быть Number или его подклассом
public class Calculator<T extends Number> {
    public double getDoubleValue(T value) {
        return value.doubleValue();
    }
}

Calculator<Integer> calc1 = new Calculator<>();
Calculator<Double> calc2 = new Calculator<>();
// Calculator<String> calc3 = new Calculator<>(); // Error! String не extends Number

Множественные границы

// T должен быть Comparable И Serializable
public class Comparator<T extends Comparable<T> & Serializable> {
    public boolean isGreater(T a, T b) {
        return a.compareTo(b) > 0;
    }
}

4. Wildcards (подстановочные типы)

? (любой тип)

public void printList(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

List<String> strings = Arrays.asList("A", "B");
List<Integer> integers = Arrays.asList(1, 2);
printList(strings);
printList(integers);

? extends T (верхняя граница)

// Читаем данные, но не пишем
public void readNumbers(List<? extends Number> numbers) {
    for (Number n : numbers) {
        System.out.println(n);
    }
}

List<Integer> integers = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.0, 2.0);
readNumbers(integers);
readNumbers(doubles);

? super T (нижняя граница)

// Пишем данные, но не гарантируем тип при чтении
public void writeNumbers(List<? super Number> numbers) {
    numbers.add(10);
    numbers.add(20.5);
}

List<Number> numList = new ArrayList<>();
List<Object> objList = new ArrayList<>();
writeNumbers(numList);
writeNumbers(objList);

5. Практические примеры

Дженерик стек

public class Stack<T> {
    private List<T> elements = new ArrayList<>();
    
    public void push(T element) {
        elements.add(element);
    }
    
    public T pop() {
        if (isEmpty()) {
            throw new EmptyStackException();
        }
        return elements.remove(elements.size() - 1);
    }
    
    public boolean isEmpty() {
        return elements.isEmpty();
    }
}

// Использование
Stack<String> stack = new Stack<>();
stack.push("First");
stack.push("Second");
System.out.println(stack.pop()); // Second

Дженерик пара

public class Pair<K, V> {
    private K key;
    private V value;
    
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
    
    public K getKey() { return key; }
    public V getValue() { return value; }
}

// Использование
Pair<String, Integer> person = new Pair<>("Alice", 30);
System.out.println(person.getKey());   // Alice
System.out.println(person.getValue()); // 30

Дженерик DAO

public interface Repository<T, ID> {
    void save(T entity);
    T findById(ID id);
    List<T> findAll();
    void delete(T entity);
}

public class UserRepository implements Repository<User, Long> {
    @Override
    public void save(User user) {
        // сохранение пользователя
    }
    
    @Override
    public User findById(Long id) {
        // поиск по ID
        return null;
    }
    
    @Override
    public List<User> findAll() {
        return new ArrayList<>();
    }
    
    @Override
    public void delete(User user) {
        // удаление
    }
}

6. Type Erasure (стирание типов)

В runtime Java стирает информацию о дженериках:

List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();

// В runtime:
// strings.getClass() == integers.getClass() == ArrayList.class
System.out.println(strings.getClass() == integers.getClass()); // true!

Поэтому нельзя создавать массивы дженериков:

// ❌ Error!
// List<String>[] array = new List<String>[10];

// ✅ Правильно:
List<String>[] array = new List[10]; // raw type

Преимущества дженериков

  1. Типобезопасность — ошибки типов выявляются в compile-time
  2. Избежание типизации — не нужны явные cast
  3. Переиспользование — один код работает с разными типами
  4. Лучшая документация — сразу видно, с какими типами работает класс

Практические рекомендации

  • Используй дженерики везде, где работаешь с коллекциями
  • Используй wildcards для большей гибкости
  • Не используй raw types без необходимости
  • Помни о type erasure при работе с reflection

Дженерики — один из самых мощных механизмов Java для написания гибкого и безопасного кода.