Как лучше обозначить тип коллекции в Java коде
Что проверяют
Проверяют принцип «программируй на уровне интерфейса»: в объявлении стоит List, а не ArrayList. Следом обычно спрашивают, какой тип вернуть из метода, что принимать параметром и чем опасен List.of().
Как отвечать
- переменные и поля: тип интерфейса слева, реализация справа,
List<String> names = new ArrayList<>();. Реализацию можно поменять одной строкой, остальной код об этом не узнает; - параметры методов: самый широкий тип, которого достаточно. Если метод только перебирает элементы, хватит
Collection<T>илиIterable<T>. Для чтения чисел любого подтипа подойдётList<? extends Number>, правило PECS: producer extends, consumer super; - возвращаемые значения: интерфейс с понятным контрактом,
List,Set,Map. Если важен порядок, с Java 21 естьSequencedCollectionиSequencedMapсgetFirst()иreversed(); - дженерики всегда: сырой
Listотключает проверку типов, и ошибка всплываетClassCastExceptionуже при выполнении. Diamond<>с Java 7 убирает повтор параметров справа; - неизменяемость:
List.of(),Map.of()(Java 9) иStream.toList()(Java 16) дают неизменяемые списки. Если вызывающий не должен менять коллекцию, это надёжнее комментария в Javadoc.
Про var: он выводит тип по правой части, поэтому var list = new ArrayList<String>() даёт переменную типа ArrayList. Для локальной переменной в коротком методе это нормально, а в полях и сигнатурах var недоступен.
Где спотыкаются
Возвращают null вместо пустой коллекции. Вызывающий код падает с NullPointerException в цикле, хотя достаточно было вернуть List.of() или Collections.emptyList().
Не знают, что List.of() бросает UnsupportedOperationException на add и NullPointerException на null-элементе, а Arrays.asList() разрешает set, но не даёт менять размер.
Выставляют ArrayList или HashMap в публичной сигнатуре. Замена реализации превращается в правку всех вызывающих мест.