Що таке 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 імплементує обидва
DataBox<Integer> box2 = new DataBox<>(); // ✅ Integer імплементує обидва
Типові помилки
- Неправильний порядок: ```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 info
- ❌ “Можна кілька класів в bounds” — Java single inheritance, максимум один клас
Пов’язані теми:
- [[11. Що таке дженерики (Generics) в Java]]
- [[13. Що таке type erasure (стирання типів)]]
- [[16. У чому різниця між <? extends T> і <? super T>]]
- [[23. Що таке recursive type bound]]
- [[26. Чи можна використовувати кілька обмежень (bounds) для одного параметра типу]]