Generics: T, ?, extends, super

Тема дорожной карты · Java

Java Generics позволяют создавать классы, интерфейсы и методы, работающие с параметрами типов, обеспечивая типобезопасность на этапе компиляции и устраняя необходимость в явных приведениях типов, которые были подвержены ошибкам в коде до Java 5. Наиболее распространённая нотация — один параметр типа <T> на классе (class Box<T>) или методе (<T> T identity(T value)), однако многопараметрические формы вроде Map<K,V> и Function<T,R> столь же широко используются в Java Collections Framework и Stream API. Wildcard-ы расширяют Java Generics с ограниченной гибкостью: List<? extends Number> (wildcard с верхней границей) принимает любой List подтипов Number и поддерживает операции чтения, тогда как List<? super Integer> (wildcard с нижней границей) принимает любой List предков Integer и поддерживает операции записи — мнемоника PECS (Producer Extends, Consumer Super) указывает, когда использовать каждый вариант. Java Generics реализованы через стирание типов: компилятор использует информацию о типах для проверки, затем заменяет параметры типов на Object (или их границу) в байткоде — это объясняет, почему instanceof List<String> недопустим и почему нельзя напрямую создавать массивы обобщённых типов (new T[10]). Понимание Java Generics является предварительным условием для эффективной работы с Collections Framework, Stream API, CompletableFuture<T>, Optional<T> и обобщённо-насыщенными библиотеками вроде Mockito и Spring's ResponseEntity<T>.

Как это работает

Generics: T, ?, extends, super покрывает generics (параметры типа: class Box<T>, List<String>, Map<K, V>), wildcards (? extends, ? super, правило PECS), функциональные интерфейсы (один abstract-метод: Function, Consumer, Supplier, Predicate), лямбды (x -> x.toUpperCase()), method references (String::toUpperCase), Stream API. Type erasure означает, что generic-инфа исчезает на рантайме — работает на compile-time, но List<String>.class тот же, что List<Integer>.class.

Когда применять

Stream API — для трансформаций (.filter().map().collect()) когда intent яснее цикла. Generics — в API библиотек чтобы протолкнуть type-safety вызывающим. Применяйте PECS (Producer Extends, Consumer Super) при проектировании wildcards. Не over-engineer — Function<? super T, ? extends R> иногда правильный ответ, иногда запах.

Типичные ошибки

Ловушки Generics: T, ?, extends, super: List rawList = new ArrayList() (raw-типы — обходят generics, ломают type-safety); злоупотребление Stream .peek() для side-эффектов (это для дебага — forEach если нужны side-эффекты); stateful-лямбды (parallel streams + mutable shared state = race); instanceof + cast вместо pattern matching (Java 21+).

Связанные понятия

Полезные ресурсы