Преобразование List<TestA> в List<TestB> в Java: подходы

Пройдите тест, узнайте какой профессии подходите и получите бесплатную карьерную консультацию
В конце подарим скидку до 55% на обучение
Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Для преобразования List<SuperType> в List<SubType> в Java, советуется использовать отфильтрованную копию, так как прямое приведение типов для дженериков не поддерживается:

Java
Скопировать код
List<SuperType> superList = ...;

List<SubType> subList = superList.stream()
                                 .filter(SubType.class::isInstance)
                                 .map(SubType.class::cast)
                                 .collect(Collectors.toList());

Использование Stream API помогает предотвратить выброс исключения ClassCastException во время выполнения благодаря проверке типов и их преобразованию с помощью методов filter и map.

Сценарии, когда прямое приведение необходимо

В некоторых случаях прямое приведение оправдано. Например, если вы абсолютно уверены, что в список добавляются только объекты SubType, или когда важна производительность и нежелательно проводить приведение каждого элемента в отдельности. В этих ситуациях возможно использование аннотации @SuppressWarnings("unchecked"), с помощью которой можно подавить предупреждения компилятора:

Java
Скопировать код
@SuppressWarnings("unchecked")
List<SubType> subList = (List<SubType>)(List<?>) superList;

Особенности обобщенных типов и типобезопасности

Типовая система Java направлена на обеспечение типобезопасности во время компиляции. Однако из-за инвариантности дженериков, список типа List<SuperType> не рассматривается как List<SubType>. Это обусловлено следующими особенностями:

  • Стирание типов: Во время выполнения информация об обобщенных типах утрачивается, и List<SuperType> и List<SubType> интерпретируются просто как List.
  • Риск исключений: Если не провести корректную проверку, то возможно выбрасывание исключения ClassCastException при попытке добавить объект SuperType, который не является SubType.

Визуализация

Пример приведения супертипов к подтипам можно сравнить с попыткой поместить разные формы в соответствующие отверстия:

Перед началом приведения типов у вас есть список:

Markdown
Скопировать код
Супертипы: [🔵🟡🟢] (Круг, Квадрат, Треугольник)
Подтипы:   []

Попытка приведения несовместимых форм:

Markdown
Скопировать код
🔵🟡🟢 (Круг, Квадрат, Треугольник пытаются стать треугольниками)

Результат приведения:

Markdown
Скопировать код
🔵 (Круг)        ❌
🟡 (Квадрат)     ❌
🟢 (Треугольник) ✅

Итог преобразования:

Markdown
Скопировать код
Супертипы: [🔵🟡] (Оставшиеся Круг и Квадрат)
Подтипы:   [🟢] (Успешно преобразованный Треугольник)

Примечание: Каждая фигура проверяется на соответствие форме, и в список подтипов попадают только те, кто прошли проверку.

Чтобы гарантированно избежать ошибок во время приведения, используйте instanceof для предотвращения исключения ClassCastException при выборке подходящих элементов:

Java
Скопировать код
List<SuperType> superTypes = new ArrayList<>();
List<SubType> subTypes = new ArrayList<>();
for (SuperType element : superTypes) {
    if (element instanceof SubType) {
        subTypes.add((SubType) element);
    }
}

Альтернативы приведению типов

Если хотите избежать приведения типов, рассмотрите следующие подходы:

  • Полиморфизм: определите методы в супертипе, которые можно переопределить в подтипе.
  • Паттерн «Посетитель»: разделяйте операции и структуру объектов.
  • Использование обобщений: создайте список с подстановочными знаками, например, List<? extends SuperType>, что сделает код более читаемым и упростит его структуру.

Работа с предупреждениями о непроверенных операциях

Если нельзя устранить предупреждения, используйте @SuppressWarnings("unchecked"):

  • Используйте подавление предупреждений только тогда, когда вы уверены в содержимом списка.
  • В своей документации объясняйте, почему использование @SuppressWarnings("unchecked") является безопасным и как вы проводили проверку типов.

Ограничения приведения типов и способы их обхода

Приведение может быть ограничено из-за особенностей дженериков и стирания типов, что усложняет ситуацию:

  • Создание массивов обобщенных типов недопустимо, например, new List<SubType>[10].
  • Оператор instanceof не работает с параметризованными типами, однако существуют определенные способы обхода этой проблемы, которые следует использовать с осторожностью.

Полезные материалы

  1. [Effective Java, 3rd Edition [Книга]](https://www.oreilly.com/library/view/effective-java-3rd/9780134686097/) — изучение основ Java для начинающих.
  2. Lesson: Generics (Updated) (The Java™ Tutorials > Learning the Java Language) — официальные учебные материалы по дженерикам в Java.
  3. java – What is PECS (Producer Extends Consumer Super)? – Stack Overflow — обсуждение концепции PECS для разработчиков на Java.
  4. [Java Generics and Collections [Книга]](https://www.oreilly.com/library/view/java-generics-and/0596527756/) — подробное обсуждение дженериков и коллекций в Java.
  5. Guidelines for Wildcard Use (The Java™ Tutorials > Learning the Java Language > Generics (Updated)) — официальное руководство по использованию подстановочных знаков.
  6. java – How do I address unchecked cast warnings? – Stack Overflow — советы о том, как заниматься предупреждениями о непроверенных приведениях типов.