Кто участвовал в декабрьском адвенте, точно помнит, что
еnum компилируется в наследник класса Enum:public enum Animal {WOLF, TIGER}
↓public class Animal extends Enum {
public static final Animal WOLF;
public static final Animal TIGER;
}
Подробнее об этом и енамах в целом можно почитать тут — раз, два и три.В определении класса
Enum используется конструкция, которая называется self-referential generic (или self-bound type, или recursive generic):EnumᐸE extends EnumᐸEᐳᐳВ этом посте расскажу, что это такое и зачем нужно.
Чтобы понять, какая проблема решается, представим, что этой конструкции нет. И определение енама выглядит так:
public abstract class MyEnum implements ComparableᐸMyEnumᐳПользователь определяет enum Animal и enum Converter. Компилятор превращает это в классы
Animal extends MyEnumКаждый класс должен реализовать интерфейс
Converter extends MyEnum
ComparableᐸMyEnumᐳ и метод compareTo. Чтобы не сравнивать животных и конвертеры, придётся использовать instanceof: public final int compareTo(MyEnum o) {
if (o instanceOf Animal other) {
// сравниваем зверюшек
// return ...
}
throw IllegalArgumentException();
}
В самом instanceOf нет ничего плохого. Тем более этот код генерируется при компиляции и остаётся за кадром.Есть более важный момент. Пользователь может спокойно сравнить животное и конвертер, ошибка возникнет только в рантайме. Это выглядит странно, ведь enum Animal и enum Converter никак не связаны между собой.
Здесь дженерик выходит на сцену:
public abstract class EnumᐸE extends EnumᐸEᐳᐳ implements ComparableᐸEᐳ🔸 Добавляем параметр E, совместимый с классом
Enum
🔸 Используем E в интерфейсе Comparable
🔸 Компилируем enum Animal вpublic class Animal extends EnumᐸAnimalᐳ🔸 Теперь
Comparable использует тип Animal, и метод compareTo станет таким:public int compareTo(Animal o)✅ Убрали
instanceOf, код стал меньше и быстрее✅ При компиляции происходит проверка типов:
Animal zebra = Animal.ZEBRA;❌
Converter csv = Converter.CSV;
zebra.compareTo(csv); // не скомпилируется!Self-referential generic позволяет использовать дочерний тип в интерфейсах и методах родителя. Для некоторых кейсов этот приём здорово упрощает код и снижает количество ошибок. В следующем посте покажу ещё один пример использования.
Ответ на вопрос перед постом: self-referential generic помогает ограничить сравнение разных enum между собой.