Что такое bounded type parameters
Вы указываете, что тип должен наследовать определённый класс или имплементировать интерфейс.
🟢 Junior Level
Bounded type parameters — это способ ограничить типы, которые можно использовать с дженериком.
Вы указываете, что тип должен наследовать определённый класс или имплементировать интерфейс.
// Без ограничения — любой тип
public class Box<T> { }
Box<String> box1 = new Box<>(); // OK
Box<Integer> box2 = new Box<>(); // OK
// С ограничением — только Number и наследники
public class NumberBox<T extends Number> { }
NumberBox<Integer> box1 = new NumberBox<>(); // ✅ OK
NumberBox<Double> box2 = new NumberBox<>(); // ✅ OK
NumberBox<String> box3 = new NumberBox<>(); // ❌ Ошибка!
Синтаксис:
T extends Class— только этот класс и наследникиT extends Interface— только классы, имплементирующие интерфейс
🟡 Middle Level
Как это работает
Один bound:
// Только Comparable типы
public class SortedList<T extends Comparable<T>> {
private List<T> list = new ArrayList<>();
public void add(T item) {
list.add(item);
list.sort(Comparable::compareTo); // можно — T точно Comparable
}
}
SortedList<Integer> ints = new SortedList<>(); // ✅ Integer implements Comparable
SortedList<String> strings = new SortedList<>(); // ✅ String implements Comparable
SortedList<Object> objs = new SortedList<>(); // ❌ Object не implements Comparable
Зачем нужно:
// Без bound — нельзя вызывать методы
public class Box<T> {
public void process(T item) {
// item.compareTo(other); // нельзя — T может быть любым
}
}
// С bound — можно вызывать методы Number
public class Stats<T extends Number> {
private List<T> numbers;
public double average() {
return numbers.stream()
.mapToDouble(Number::doubleValue) // ✅ можно!
.average().orElse(0);
}
}
Multiple bounds
Можно указать несколько ограничений:
// T должен быть Serializable И Comparable
public class DataBox<T extends Comparable<T> & Serializable> {
private T value;
public int compare(T other) {
return value.compareTo(other); // ✅
}
public byte[] serialize() {
// ✅ можно сериализовать — T extends Serializable
return serializeValue(value);
}
private static byte[] serializeValue(Serializable value) {
// стандартная Java сериализация
try (var baos = new java.io.ByteArrayOutputStream();
var oos = new java.io.ObjectOutputStream(baos)) {
oos.writeObject(value);
return baos.toByteArray();
} catch (java.io.IOException e) {
throw new RuntimeException(e);
}
}
}
DataBox<String> box = new DataBox<>(); // ✅ String implements оба
DataBox<Integer> box2 = new DataBox<>(); // ✅ Integer implements оба
Типичные ошибки
- Неправильный порядок: ```java // ❌ Класс должен быть первым public class Bad<T extends Serializable & Number> {} // error // JLS требует: класс (если есть) должен быть первым, т.к. erasure использует // первый bound. В Java single inheritance — только один класс допустим.
// ✅ Сначала класс, потом интерфейсы public class Good<T extends Number & Serializable> {} // OK
2. **Несуществующий bound:**
```java
// ❌ Integer — final класс, не может быть bound для наследования
public class Box<T extends Integer> {} // technically OK но бесполезно
🔴 Senior Level
Internal Implementation
Type erasure с bounds:
public class Stats<T extends Number> {
private T value;
public double doubleValue() { return value.doubleValue(); }
}
// После erasure: T -> Number (первый bound)
public class Stats {
private Number value;
public double doubleValue() { return value.doubleValue(); }
}
Multiple bounds erasure:
public class Box<T extends Comparable<T> & Serializable> { }
// Erasure: T -> первый bound (Comparable)
public class Box {
private Comparable value;
}
Архитектурные Trade-offs
Ограничения vs unbounded:
| Bounded | Unbounded |
|---|---|
| Можно вызывать методы | Нельзя вызывать методы |
| Type-safe | Менее type-safe |
| Ограниченная гибкость | Максимальная гибкость |
Edge Cases
1. Recursive bounds:
// T должен быть Comparable к самому себе
public class Enum<E extends Enum<E>> implements Comparable<E> {
// Java enum internal
}
// Self-bounding types
public class SelfBounded<T extends SelfBounded<T>> {
private T self;
public T set(T self) {
this.self = self;
return self;
}
}
2. Wildcard vs bounded parameter:
// Bounded type parameter — для классов/методов
public class NumberBox<T extends Number> { }
// Bounded wildcard — для параметров
public void process(List<? extends Number> numbers) { }
// Разница:
// - <T extends Number> — можно использовать T в методах
// - <? extends Number> — нельзя создавать новые элементы
3. Bridge methods с bounds:
public class Node<T extends Comparable<T>> {
private T data;
public T getData() { return data; }
public void setData(T data) { this.data = data; }
}
public class DateNode extends Node<Date> {
@Override
public Date getData() { return super.getData(); }
@Override
public void setData(Date data) { super.setData(data); }
// Bridge method после erasure:
// public void setData(Comparable data) { setData((Date) data); }
}
Производительность
Bounded types:
- Runtime: Zero overhead
- Compile time: дополнительная проверка
- Type erasure: bound становится типом после компиляции
Bounded vs unbounded:
- Одинаково в runtime
- Bounded даёт больше информации компилятору
// После type erasure bound становится compile-time проверкой.
// JIT не видит разницы между bounded и unbounded — bytecode одинаковый.
// Польза bounds только в compile-time type safety.
Production Experience
Generic Repository:
public interface BaseEntity<ID extends Serializable> {
ID getId();
}
public abstract class JpaRepository<T extends BaseEntity<ID>, ID extends Serializable> {
private final Class<T> entityType;
public Optional<T> findById(ID id) {
// type-safe findById
return entityManager.find(entityType, id);
}
public <S extends T> S save(S entity) {
if (entity.getId() == null) {
return persist(entity);
} else {
return merge(entity);
}
}
}
Builder pattern с bounds:
public abstract class Builder<T, B extends Builder<T, B>> {
protected abstract B self();
public B withName(String name) {
// ... setup
return self();
}
public T build() {
// ... build
}
}
public class UserBuilder extends Builder<User, UserBuilder> {
@Override
protected UserBuilder self() { return this; }
}
User user = new UserBuilder()
.withName("John")
.build();
Best Practices
// ✅ Bounded для вызова методов
public class Stats<T extends Number> {
public double average() { /* использовать Number::doubleValue */ }
}
// ✅ Multiple bounds для нескольких контрактов
public class DataBox<T extends Comparable<T> & Serializable> {}
// ✅ Recursive bounds для self-types
public class Enum<E extends Enum<E>> {}
// ❌ Слишком строгие bounds
// ❌ Bounds на raw types
🎯 Шпаргалка для интервью
Обязательно знать:
- Bounded type parameters:
<T extends Number>— только Number и наследники - Позволяют вызывать методы bound-типа:
value.doubleValue()дляT extends Number - Multiple bounds:
<T extends Comparable<T> & Serializable>— класс первый, потом интерфейсы - Type erasure использует первый bound:
T extends Number & Serializable-> erasure = Number - Recursive bounds:
<E extends Enum<E>>— тип сравнивается с самим собой - JLS требует: класс (если есть) должен быть первым в списке bounds
Частые уточняющие вопросы:
- Зачем нужны bounded types? — Чтобы вызывать методы bound-типа внутри generic класса
- Почему класс должен быть первым в multiple bounds? — Erasure использует первый bound, single inheritance
- Что такое recursive bound? —
T extends Comparable<T>— тип сравнивается с таким же типом - Relaxed recursive bound? —
T extends Comparable<? super T>— позволяет наследникам тоже работать
Красные флаги (НЕ говорить):
- ❌ “Можно поставить интерфейс перед классом в bounds” — JLS требует класс первым
- ❌ “Multiple bounds дают runtime overhead” — Zero overhead, bound только compile-time
- ❌ “Bounded и unbounded одинаковы в runtime” — Одинаковы после erasure, но bounds дают больше compile-time инфо
- ❌ “Можно несколько классов в bounds” — Java single inheritance, максимум один класс
Связанные темы:
- [[11. Что такое дженерики (Generics) в Java]]
- [[13. Что такое type erasure (стирание типов)]]
- [[16. В чём разница между <? extends T> и <? super T>]]
- [[23. Что такое recursive type bound]]
- [[26. Можно ли использовать несколько ограничений (bounds) для одного параметра типа]]