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

Что такое наследование в дженериках?

2.0 Middle🔥 111 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью

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

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

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

Наследование в дженериках

Наследование в дженериках — это механизм, который позволяет определять иерархию типов с использованием параметров типов (type parameters). Это включает создание обобщённых классов и интерфейсов, которые наследуют друг друга и используют тип-параметры в своих определениях.

Основной концепт

Дженерики НЕ наследуются как примитивные типы! List<String> НЕ является подтипом List<Object>, хотя String наследует Object.

// ❌ ОШИБКА: типы не совместимы
List<String> strings = Arrays.asList("a", "b");
List<Object> objects = strings;  // Compilation error!

// ✅ Правильно
List<Object> objects = new ArrayList<Object>();  // Explicit type
List<?> wildcard = strings;  // Wildcard type

Наследование с дженериками: практические примеры

1. Простое наследование обобщённого класса

// Базовый обобщённый класс
public class Container<T> {
    private T value;
    
    public Container(T value) {
        this.value = value;
    }
    
    public T getValue() {
        return value;
    }
    
    public void setValue(T value) {
        this.value = value;
    }
}

// Подкласс, который сохраняет параметр типа
public class StringContainer extends Container<String> {
    public StringContainer(String value) {
        super(value);
    }
    
    // Добавляем специфичные методы
    public int getLength() {
        return getValue().length();
    }
}

// Использование
StringContainer container = new StringContainer("Hello");
String value = container.getValue();  // String, не Object!
int length = container.getLength();   // 5

2. Наследование с сохранением параметра типа

// Базовый класс
public class Repository<T> {
    protected List<T> items = new ArrayList<>();
    
    public void add(T item) {
        items.add(item);
    }
    
    public List<T> getAll() {
        return new ArrayList<>(items);
    }
    
    public T findFirst() {
        return items.isEmpty() ? null : items.get(0);
    }
}

// Подкласс сохраняет параметр T
public class UserRepository extends Repository<User> {
    // Сохраняется T = User
    
    public User findByEmail(String email) {
        return items.stream()
            .filter(user -> user.getEmail().equals(email))
            .findFirst()
            .orElse(null);
    }
}

// Использование
UserRepository repo = new UserRepository();
repo.add(new User("john@example.com"));
User user = repo.findFirst();  // ✅ Тип User
List<User> users = repo.getAll();  // ✅ List<User>

3. Наследование с добавлением нового параметра типа

// Базовый класс с одним параметром
public class Pair<K> {
    protected K key;
    
    public Pair(K key) {
        this.key = key;
    }
    
    public K getKey() {
        return key;
    }
}

// Подкласс добавляет ещё один параметр
public class KeyValuePair<K, V> extends Pair<K> {
    private V value;
    
    public KeyValuePair(K key, V value) {
        super(key);
        this.value = value;
    }
    
    public V getValue() {
        return value;
    }
}

// Использование
KeyValuePair<String, Integer> pair = new KeyValuePair<>("age", 25);
String key = pair.getKey();      // ✅ String
Integer value = pair.getValue(); // ✅ Integer

4. Многоуровневое наследование

// Уровень 1: базовый класс
public class Entity<ID> {
    protected ID id;
    
    public ID getId() {
        return id;
    }
}

// Уровень 2: специализированный
public class DomainObject<ID> extends Entity<ID> {
    protected String name;
    
    public String getName() {
        return name;
    }
}

// Уровень 3: конкретная реализация
public class User extends DomainObject<Long> {
    private String email;
    
    public User(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
}

// Использование
User user = new User(1L, "John", "john@example.com");
Long id = user.getId();        // ✅ Long
String name = user.getName();  // ✅ String

Wildcards и наследование

// Covariance: ? extends Type
public void printNumbers(List<? extends Number> numbers) {
    for (Number num : numbers) {
        System.out.println(num);
    }
}

// Использование
List<Integer> integers = Arrays.asList(1, 2, 3);
printNumbers(integers);  // ✅ Integer extends Number

List<Double> doubles = Arrays.asList(1.0, 2.0);
printNumbers(doubles);   // ✅ Double extends Number

// Contravariance: ? super Type
public void addNumbers(List<? super Integer> list) {
    list.add(10);
    list.add(20);
}

// Использование
List<Integer> integers = new ArrayList<>();
addNumbers(integers);  // ✅ Integer

List<Number> numbers = new ArrayList<>();
addNumbers(numbers);   // ✅ Integer is a Number

Интерфейсы и дженерики

// Базовый интерфейс
public interface Comparable<T> {
    int compareTo(T other);
}

// Реализация интерфейса
public class Student implements Comparable<Student> {
    private String name;
    private double gpa;
    
    public Student(String name, double gpa) {
        this.name = name;
        this.gpa = gpa;
    }
    
    @Override
    public int compareTo(Student other) {
        return Double.compare(this.gpa, other.gpa);
    }
}

// Использование
Student s1 = new Student("Alice", 3.8);
Student s2 = new Student("Bob", 3.5);
int result = s1.compareTo(s2);  // ✅ Типизировано как Student

Ограничения на параметры типов (Bounded Type Parameters)

// Ограничение: T должен быть подтипом Number
public class NumberContainer<T extends Number> {
    private T value;
    
    public NumberContainer(T value) {
        this.value = value;
    }
    
    public double doubleValue() {
        return value.doubleValue();
    }
}

// Использование
NumberContainer<Integer> intContainer = new NumberContainer<>(42);
NumberContainer<Double> doubleContainer = new NumberContainer<>(3.14);
// NumberContainer<String> stringContainer = new NumberContainer<>("abc");  // ❌ Error!

// Множественные ограничения
public class MultiRestrictedClass<T extends Number & Comparable<T>> {
    // T должен быть Number И реализовать Comparable
}

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

// Пример: иерархия репозиториев
public abstract class BaseRepository<T, ID> {
    protected List<T> data = new ArrayList<>();
    
    public void save(T entity) {
        data.add(entity);
    }
    
    public List<T> findAll() {
        return new ArrayList<>(data);
    }
    
    public abstract void delete(ID id);
}

public class UserRepository extends BaseRepository<User, Long> {
    @Override
    public void delete(Long id) {
        data.removeIf(user -> user.getId().equals(id));
    }
    
    public User findByEmail(String email) {
        return data.stream()
            .filter(user -> user.getEmail().equals(email))
            .findFirst()
            .orElse(null);
    }
}

public class ProductRepository extends BaseRepository<Product, String> {
    @Override
    public void delete(String sku) {
        data.removeIf(product -> product.getSku().equals(sku));
    }
}

Type Erasure и наследование

// Important: Type erasure occurs at runtime!
public class GenericClass<T> {
    private T value;
    
    // ❌ Невозможно: нельзя создать экземпляр параметра типа
    // public T createInstance() { return new T(); }
    
    // ❌ Невозможно: перегрузка по параметру типа
    // public void method(List<String> list) { }
    // public void method(List<Integer> list) { }  // Same after erasure
    
    // ✅ Возможно: использование instanceof с wildcards
    public boolean isString(Object obj) {
        return obj instanceof String;
    }
}

Лучшие практики

  1. Сохраняй параметры типа в подклассах для типобезопасности
  2. Используй wildcards когда нужна гибкость (? extends / ? super)
  3. Ограничивай параметры типов (extends) если требуется конкретное поведение
  4. Избегай Raw Types — всегда указывай параметры типов
  5. Используй обобщённые интерфейсы для создания типобезопасного API
  6. Помни о type erasure — информация о типах теряется в runtime

Наследование в дженериках — это мощный инструмент для создания типобезопасной, переиспользуемой архитектуры в Java.

Что такое наследование в дженериках? | PrepBro