← Назад к вопросам
Что такое наследование в дженериках?
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;
}
}
Лучшие практики
- Сохраняй параметры типа в подклассах для типобезопасности
- Используй wildcards когда нужна гибкость (? extends / ? super)
- Ограничивай параметры типов (extends) если требуется конкретное поведение
- Избегай Raw Types — всегда указывай параметры типов
- Используй обобщённые интерфейсы для создания типобезопасного API
- Помни о type erasure — информация о типах теряется в runtime
Наследование в дженериках — это мощный инструмент для создания типобезопасной, переиспользуемой архитектуры в Java.