Для чего нужны дженерики в Java?
Что проверяют
iOS-разработчику такой вопрос задают, чтобы понять, отделяете ли вы идею обобщённого кода от её реализации в конкретном языке. Следом почти наверняка спросят, чем дженерики Java отличаются от дженериков Swift, с которыми вы работаете каждый день.
Как отвечать
Идея общая: параметр типа позволяет написать код один раз и получить проверку типов при компиляции.
- типобезопасность:
List<String>не даст положить туда число, ошибка будет при компиляции, а неClassCastExceptionв рантайме; - без приведений: до Java 5 коллекции хранили
Object, и каждое чтение требовало явного приведения типа; - переиспользование: один
Box<T>или метод<T extends Comparable<T>> T max(List<T> list)работает для любых подходящих типов; - wildcards:
? extends Tдля чтения,? super Tдля записи, правило PECS (Producer Extends, Consumer Super).
Главная особенность Java это стирание типов (type erasure). После компиляции List<String> и List<Integer> это один класс, параметра в рантайме нет. Поэтому нельзя написать new T(), проверить instanceof List<String> или объявить List<int>, только с обёрткой Integer.
Дальше мост к Swift, и здесь вы набираете очки. В Swift информация о типе в рантайме доступна (T.self), компилятор может специализировать обобщённый код под конкретный тип, а параметром спокойно бывает структура без упаковки: Array<Int> хранит значения подряд, без боксинга. Вместо wildcards там ограничения через протоколы и where, плюс associatedtype в протоколах, непрозрачные типы some и экзистенциальные any.
Где спотыкаются
Не знают про стирание и удивляются, почему нельзя перегрузить метод для List<String> и List<Integer>: после стирания сигнатуры совпадают.
Переносят на Java привычки из Swift и думают, что List<int> допустим или что тип параметра можно узнать в рантайме.
Путают ? extends и ? super. Пример, который быстро всё расставляет: Collections.copy(List<? super T> dest, List<? extends T> src), из одного списка читаем, в другой пишем.