Питання 23 · Розділ 20

Що таке recursive type bound

Structured Java interview answer with junior, middle, and senior-level explanation.

Мовні версії: English Russian Ukrainian

🟢 Junior Level

Recursive type bound — це коли type parameter обмежений самим собою або типом, який використовує цей самий parameter.

// T повинен бути 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);
    }
}

SortedList<Integer> ints = new SortedList<>();    // ✅ Integer implements Comparable<Integer>
SortedList<String> strings = new SortedList<>();  // ✅ String implements Comparable<String>

Проста аналогія: “Я можу порівнюватися з такими ж, як я”


🟡 Middle Level

Як це працює

Класичний приклад — Enum:

// Java Enum declaration
public abstract class Enum<E extends Enum<E>>
    implements Comparable<E> {

    public final int compareTo(E other) {
        return this.ordinal - other.ordinal;
    }
}

// Використання
enum Color { RED, GREEN, BLUE }
// Color extends Enum<Color>
// Color implements Comparable<Color>

Навіщо це потрібно:

// Без recursive bound — не можна гарантувати type safety
public class Box<T extends Comparable<T>> {
    private T value;

    public int compare(T other) {
        return value.compareTo(other);  // ✅ type-safe
    }
}

Box<Integer> box = new Box<>();
box.compare(42);  // OK

// Box<Object> box2 = new Box<>();  // ❌ Object не implements Comparable<Object>

Self-bounding types

Патерн для fluent interfaces:

public abstract class Builder<T, B extends Builder<T, B>> {
    protected abstract B self();

    public B withName(String name) {
        // ... setup
        return self();
    }

    public B withAge(int age) {
        // ... setup
        return self();
    }

    public T build() {
        // ... build
    }
}

public class UserBuilder extends Builder<User, UserBuilder> {
    @Override
    protected UserBuilder self() { return this; }
}

// Fluent API
User user = new UserBuilder()
    .withName("John")
    .withAge(25)
    .build();

Типові помилки

  1. Неправильний recursive bound: ```java // ❌ T extends Comparable — не recursive public class BadBox<T extends Comparable> { }

// ✅ T extends Comparable — recursive public class GoodBox<T extends Comparable> { }


2. **Занадто строгий bound:**
```java
// ❌ T повинен бути точно Comparable<T>
public class Box<T extends Comparable<T>> { }

// ⚠️ Не пройде для деяких типів
// Деякі класи реалізують Comparable до super типу

🔴 Senior Level

Internal Implementation

Type erasure recursive bounds:

public class Box<T extends Comparable<T>> {
    private T value;
}

// Після erasure: T -> Comparable (перший bound)
public class Box {
    private Comparable value;
}

Nested recursive bounds:

// Складний випадок — Enum<E extends Enum<E>>
// T повинен бути Enum<T>
// Enum<T> implements Comparable<T>
// Отже T implements Comparable<T>

public class Enum<E extends Enum<E>> implements Comparable<E> {
    // E — це конкретний enum тип
    // compareTo приймає той самий enum тип
}

Архітектурні Trade-offs

Recursive bounds vs звичайні:

Recursive Звичайні
Type-safe порівняння Може бути менш точним
Fluent interfaces Simpler
Складніше зрозуміти Простіше

Edge Cases

1. Multiple recursive bounds:

public class Graph<N extends Node<N, E>, E extends Edge<N, E>> {
    private List<N> nodes;
    private List<E> edges;
}

public interface Node<N extends Node<N, E>, E extends Edge<N, E>> {
    List<E> getEdges();
}

public interface Edge<N extends Node<N, E>, E extends Edge<N, E>> {
    N getSource();
    N getTarget();
}

2. Relaxed recursive bound:

// T повинен бути Comparable до super типу T
public class SortedList<T extends Comparable<? super T>> {
    // Це relaxes bound — дозволяє нащадкам теж працювати
}

// ? super T потрібен тому що підкласи наслідують Comparable<SuperType>.
// Наприклад, Dog extends Animal implements Comparable<Animal>.
// Без ? super T Dog не задовольняв би Comparable<Dog>.

// Наприклад:
class Date implements Comparable<Date> { }
class java.sql.Date extends Date { }
// java.sql.Date implements Comparable<Date> (через наслідування)
// Але не Comparable<java.sql.Date>
// З ? super T — працює!

3. CRTP (Curiously Recurring Template Pattern):

// Реальне використання: Enum<E extends Enum<E>> — кожен enum тип
// порівнюється тільки з собою. Це гарантує type-safe compareTo.

// Патерн з C++, адаптований для Java
public abstract class SelfComparable<T extends SelfComparable<T>>
    implements Comparable<T> {

    public abstract int compareTo(T other);
}

public class MyType extends SelfComparable<MyType> {
    private int value;

    @Override
    public int compareTo(MyType other) {
        return Integer.compare(this.value, other.value);
    }
}

Продуктивність

Recursive bounds:
- Runtime: Zero overhead
- Compile time: складна перевірка
- Type erasure: bound стає типом

Продуктивність однакова для всіх bound типів

Production Experience

JDK приклади:

// Enum — класичний recursive bound
public abstract class Enum<E extends Enum<E>>
    implements Comparable<E> { }

// Collections.max
public static <T extends Object & Comparable<? super T>> T max(
    Collection<? extends T> coll
) { }

// Builder pattern
public abstract class AbstractQueryBuilder<B extends AbstractQueryBuilder<B>> {
    protected abstract B self();

    public B where(String condition) {
        // ...
        return self();
    }
}

Real-world example:

// Entity з self-type для builder
public abstract class BaseEntity<E extends BaseEntity<E>> {
    private Long id;

    @SuppressWarnings("unchecked")
    public E withId(Long id) {
        this.id = id;
        return (E) this;
    }
}

public class User extends BaseEntity<User> {
    private String name;

    public User withName(String name) {
        this.name = name;
        return this;
    }
}

User user = new User()
    .withId(1L)
    .withName("John");

Best Practices

// ✅ Recursive bound для порівняння
public class SortedList<T extends Comparable<T>> { }

// ✅ Relaxed bound для нащадків
public class SortedList<T extends Comparable<? super T>> { }

// ✅ Self-bounding для fluent API
public abstract class Builder<B extends Builder<B>> {
    protected abstract B self();
}

// ❌ Занадто складні recursive bounds
// ❌ Recursive bounds без необхідності

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • Recursive type bound: <T extends Comparable<T>> — тип обмежений самим собою
  • Класичний приклад — Enum<E extends Enum<E>> — кожен enum порівнюється з собою
  • Self-bounding types для fluent API: Builder<B extends Builder<B>> повертає B
  • Relaxed recursive bound: <T extends Comparable<? super T>> — дозволяє нащадкам
  • CRTP (Curiously Recurring Template Pattern) — патерн з C++, адаптований для Java
  • Type erasure: T extends Comparable<T> -> erasure = Comparable

Часті уточнюючі запитання:

  • Навіщо потрібен recursive bound? — Гарантувати type-safe порівняння: T порівнюється з T, не з Object
  • Чому Enum використовує recursive bound? — Щоб Color.compareTo(Color) приймав Color, не Enum
  • Що таке relaxed recursive bound?? super T дозволяє підкласам наслідувати Comparable
  • Де використовується self-bounding? — Fluent builder pattern: withName().withAge().build()

Червоні прапорці (НЕ говорити):

  • ❌ “Recursive bound створює нескінченну рекурсію” — Це compile-time обмеження, не runtime
  • ❌ “Enum<E extends Enum> — дивина Java" — Це гарантує type-safe compareTo
  • ❌ “Self-bounding — це тільки для builder” — Також для graph nodes, entities, comparables
  • ❌ “Relaxed bound менш безпечний” — ? super T безпечніший, дозволяє більше типів

Пов’язані теми:

  • [[11. Що таке дженерики (Generics) в Java]]
  • [[15. Що таке bounded type parameters]]
  • [[17. Що таке PECS (Producer Extends Consumer Super)]]
  • [[26. Чи можна використовувати кілька обмежень (bounds) для одного параметра типу]]