Найефективніший спосіб передати список <SubClass> до списку <BaseClass>


134

У мене є таке, до List<SubClass>якого я хочу ставитися як до List<BaseClass>. Здається, це не повинно бути проблемою, оскільки передавання на a SubClass- BaseClassце зовсім нескладно, але мій компілятор скаржиться, що подання неможливо.

Отже, який найкращий спосіб отримати посилання на ті самі об’єкти, що і на List<BaseClass>?

Зараз я просто складаю новий список і копіюю старий список:

List<BaseClass> convertedList = new ArrayList<BaseClass>(listOfSubClass)

Але як я розумію, це має створити абсолютно новий список. Я хотів би, якщо можливо, посилання на оригінальний список!


3
Відповідь ви тут: stackoverflow.com/questions/662508 / ...
lukastymo

Відповіді:


175

У синтаксисі цього типу призначення використовується підстановка:

List<SubClass> subs = ...;
List<? extends BaseClass> bases = subs;

Важливо розуміти , що List<SubClass>це НЕ взаємозамінні з List<BaseClass>. Код, який зберігає посилання на List<SubClass>заповіт, очікує, що кожен елемент у списку буде а SubClass. Якщо інша частина коду, зазначена у списку як List<BaseClass>,, компілятор не подаватиме скарги, коли вставляється BaseClassабо AnotherSubClassАле це призведе ClassCastExceptionдо першого фрагменту коду, який передбачає, що все в списку є a SubClass.

Загальні колекції ведуть себе не так, як масиви на Java. Масиви є коваріантними; тобто дозволено це робити:

SubClass[] subs = ...;
BaseClass[] bases = subs;

Це дозволено, оскільки масив "знає" тип його елементів. Якщо хтось спробує зберегти щось, що не є екземпляром, SubClassу масиві (через basesпосилання), буде викинуто виняток із виконання.

Родові колекції не «знають» свій тип компонента; ця інформація "стирається" під час компіляції. Тому вони не можуть підняти виняток із виконання часу, коли трапляється недійсний магазин. Натомість, ClassCastExceptionбуде піднято в якийсь далекий, важко асоційований пункт у коді, коли значення буде прочитане з колекції. Якщо ви прислухаєтесь до попереджень компілятора про безпеку типу, ви уникатимете цих помилок під час виконання.


Слід зазначити, що за допомогою масивів замість ClassCastException при отриманні об'єкта, який не має типу SubClass (або похідного), ви отримаєте ArrayStoreException при вставці.
Аксель

Особливо дякую за пояснення, чому два списки не можна вважати однаковими.
Райлі Ларк

Система типу Java робить вигляд, що масиви є коваріантними, але вони насправді не підмінні, що підтверджено ArrayStoreException.
Paŭlo Ebermann

38

Еріксон вже пояснив, чому ви не можете цього зробити, але ось деякі рішення:

Якщо ви хочете лише вийняти елементи зі свого базового списку, в принципі ваш спосіб отримання повинен бути оголошений як такий, що приймає List<? extends BaseClass>.

Але якщо це не так і ви не можете його змінити, ви можете обернути список Collections.unmodifiableList(...), що дозволяє повернути Список супертипу параметру аргументу. (Це дозволяє уникнути проблеми безпеки типу, кидаючи UnsupportedOperationException при спробах вставки.)


Чудово! не знав цього.
keuleJ

Велике спасибі!
Воланд

14

Як пояснив @erickson, якщо ви дійсно хочете посилання на оригінальний список, переконайтеся, що жоден код не вставляє нічого до цього списку, якщо ви хочете знову використовувати його під його початковою декларацією. Найпростіший спосіб отримати його - просто занести його до простого старого негенеріального списку:

List<BaseClass> baseList = (List)new ArrayList<SubClass>();

Я б не рекомендував цього, якщо ви не знаєте, що відбувається зі списком, і запропонував би вам змінити будь-який код, який потрібен Списку, щоб прийняти список, який у вас є.


3

Нижче - корисний фрагмент, який працює. Він створює новий список масивів, але створення об'єктів JVM над головою є несуттєвим.

Я бачив, що інші відповіді не обов'язково складні.

List<BaseClass> baselist = new ArrayList<>(sublist);

1
Дякуємо за цей фрагмент коду, який може надати негайну допомогу. Правильне пояснення значно покращило б його навчальну цінність, показавши, чому це хороше рішення проблеми, і зробило б її кориснішою для майбутніх читачів із подібними, але не однаковими питаннями. Будь ласка, відредагуйте свою відповідь, щоб додати пояснення та вказати, які обмеження та припущення застосовуються. Зокрема, чим це відрізняється від коду у питанні, що складає новий список?
Toby Speight

Накладні витрати не є незначними, оскільки вони також повинні (неглибоко) копіювати кожне посилання. Як такий, він масштабується з розміром списку, тому це операція O (n).
john16384

2

Я пропустив відповідь, де ви тільки що подали оригінальний список, використовуючи подвійний акторський склад. Тож ось для повноти:

List<BaseClass> baseList = (List<BaseClass>)(List<?>)subList;

Нічого не копіюється, і операція швидка. Однак ви тут обмацуєте компілятор, тому ви повинні повністю переконатись, що ви не змінюєте список таким чином, щоб subListпуски містили елементи іншого підтипу. У роботі з незмінними списками це, як правило, не проблема.


1

List<BaseClass> convertedList = Collections.checkedList(listOfSubClass, BaseClass.class)


5
Це не повинно працювати, оскільки для цього методу всі аргументи та результат приймають параметр одного типу.
Paŭlo Ebermann

дивно, ти маєш рацію. чомусь я був вражений, що цей метод був корисним для оновлення загальної колекції. все ще може бути використаний таким чином, і це забезпечить "безпечну" колекцію, якщо ви хочете використовувати придушення попередження.
jtahlborn

1

Те, що ви намагаєтеся зробити, дуже корисно, і я вважаю, що мені потрібно це робити дуже часто в коді, який я пишу. Приклад використання:

Скажімо, у нас є інтерфейс, Fooі у нас є zorkingпакет, ZorkingFooManagerякий створює і управляє екземплярами пакет-приватний ZorkingFoo implements Foo. (Дуже поширений сценарій.)

Отже, ZorkingFooManagerповинен містити, private Collection<ZorkingFoo> zorkingFoosале його потрібно виставити public Collection<Foo> getAllFoos().

Більшість програмістів Java не замислювалися б двічі перед тим, getAllFoos()як реалізувати, як виділити нове ArrayList<Foo>, заповнивши його всіма елементами zorkingFoosта повернути його. Мені подобається розважати думку про те, що приблизно 30% усіх циклів годин, що споживаються кодом java, який працює на мільйонах машин по всій планеті, не робить нічого, крім створення таких марних копій ArrayLists, які після їх створення збирають сміття мікросекундами.

Вирішення цієї проблеми - це, звичайно, колекціонування колекції. Ось найкращий спосіб зробити це:

static <T,U extends T> List<T> downCastList( List<U> list )
{
    return castList( list );
}

Що приводить нас до castList()функції:

static <T,E> List<T> castList( List<E> list )
{
    @SuppressWarnings( "unchecked" )
    List<T> result = (List<T>)list;
    return result;
}

Проміжна resultзмінна необхідна через збочення мови java:

  • return (List<T>)list;створює виняток "неперевірений ролик"; все йде нормально; але з іншого боку:

  • @SuppressWarnings( "unchecked" ) return (List<T>)list; - незаконне використання анотації придушення-попередження.

Отже, навіть якщо це не кошерніше використовувати @SuppressWarningsв returnоператорі, це, мабуть, добре використовувати його у призначенні, тому додаткова змінна "результат" вирішує цю проблему. (У будь-якому випадку його слід оптимізувати або компілятором, або JIT.)


0

Щось подібне також має працювати:

public static <T> List<T> convertListWithExtendableClasses(
    final List< ? extends T> originalList,
    final Class<T> clazz )
{
    final List<T> newList = new ArrayList<>();
    for ( final T item : originalList )
    {
        newList.add( item );
    }// for
    return newList;
}

Не знаю, для чого потрібен клазз у Eclipse ..


0

Як щодо кастингу всіх елементів. Він створить новий список, але посилатиметься на вихідні об'єкти зі старого списку.

List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());

Це повний фрагмент коду, який працює для передачі списку підкласів до суперкласу.
kanaparthikiran

0

Це повний робочий фрагмент коду, що використовує Generics, щоб передати список підкласів до суперкласу.

Метод виклику, який передає тип підкласу

List<SubClass> subClassParam = new ArrayList<>();    
getElementDefinitionStatuses(subClassParam);

Метод Callee, який приймає будь-який підтип базового класу

private static List<String> getElementDefinitionStatuses(List<? extends 
    BaseClass> baseClassVariableName) {
     return allElementStatuses;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.