Параметризованные классы
Определение
Параметризованный класс получает тип как параметр и использует его в полях и методах. Так один алгоритм работает с разными типами, а компилятор проверяет согласованность.
public final class Box<T> {
private final T value;
public Box(T value) {
this.value = value;
}
public T value() {
return value;
}
}
Box<String> text = new Box<>("ready");
String value = text.value(); // приведение не нужноT — не специальное слово, а имя параметра типа. Для нескольких ролей часто используют K, V, E, но говорящее имя тоже допустимо.
Ограничения типа
Bound сообщает, какие операции доступны внутри класса:
public final class Range<T extends Comparable<? super T>> {
private final T min;
private final T max;
public Range(T min, T max) {
if (min.compareTo(max) > 0) {
throw new IllegalArgumentException("min > max");
}
this.min = min;
this.max = max;
}
}Несколько ограничений записываются через &; класс, если он есть, должен стоять первым:
<T extends Number & Comparable<T>>Инвариантность и wildcard
Даже если Integer наследует Number, Box<Integer> не является подтипом Box<Number>. Иначе в коробку целых чисел можно было бы положить Double.
Для отношений между параметризованными типами применяют wildcard:
double sum(List<? extends Number> source) {
return source.stream()
.mapToDouble(Number::doubleValue)
.sum();
}
void addDefaults(List<? super Integer> target) {
target.add(0);
}Правило PECS:
- producer —
? extends T: безопасно читатьT, нельзя добавлять произвольныйT; - consumer —
? super T: можно добавлятьT, читать можно только какObject.
Стирание типов
Java реализует generics в основном через стирание. После компиляции JVM обычно не хранит аргумент T у обычного объекта:
- нельзя создать
new T(); - нельзя написать
new T[10]; - нельзя проверить
instanceof Box<String>; - статическое поле не может иметь тип параметра класса — статическое состояние общее для всех экземпляров.
// Нельзя:
// private static T cached;
// T[] values = new T[10];При необходимости фабрику передают явно:
static <T> T create(Supplier<T> factory) {
return factory.get();
}Generic-класс и generic-метод
Метод может иметь собственный параметр типа независимо от класса:
public static <T> Box<T> of(T value) {
return new Box<>(value);
}<T> до возвращаемого типа объявляет параметр метода. Diamond <> позволяет компилятору вывести аргумент типа из контекста.
Ловушки
- Считать
Container<Child>подтипомContainer<Parent>. - Использовать raw type и терять проверку компилятора.
- Пытаться создавать
Tили массивTбез переданной фабрики/типа. - Возвращать wildcard без необходимости: вызывающему коду труднее использовать результат.
- Путать ограничение
<T extends Number>с wildcard? extends Number: первое объявляет тип, второе описывает семейство уже параметризованных типов.
Вопросы на собеседовании
- Зачем параметризовать класс?
- Почему
List<Integer>не являетсяList<Number>? - Как работает PECS?
- Что означает стирание типов и какие ограничения оно создаёт?
- Чем параметр типа метода отличается от параметра типа класса?
- Почему нельзя создать
new T()?
Связанные заметки
Обобщения generics · Raw Type · List · Функциональные интерфейсы · Преобразование типов Java