Самый эффективный способ привести List <SubClass> к List <BaseClass>

134

У меня есть файл, List<SubClass>который я хочу рассматривать как List<BaseClass>. Похоже, это не должно быть проблемой, поскольку приведение a SubClassк a 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для первого фрагмента кода, который предполагает, что все в списке - это SubClass.

Универсальные коллекции не ведут себя так же, как массивы в Java. Массивы ковариантны; то есть разрешено это делать:

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

Это разрешено, потому что массив «знает» тип своих элементов. Если кто-то попытается сохранить что-то, что не является экземпляром SubClassв массиве (через basesссылку), будет выдано исключение времени выполнения.

Универсальные коллекции не «знают» свой тип компонента; эта информация «стирается» во время компиляции. Следовательно, они не могут вызвать исключение времени выполнения при возникновении недопустимого хранилища. Вместо этого, ClassCastExceptionкогда значение считывается из коллекции , будет возникать в какой-то далекой, трудно ассоциируемой точке кода. Если вы прислушаетесь к предупреждениям компилятора о безопасности типов, вы избежите этих ошибок типа во время выполнения.

Эриксон
источник
Следует отметить, что с Arrays вместо ClassCastException при извлечении объекта, который не является типом SubClass (или производным), вы получите ArrayStoreException при вставке.
Axel
Особенно спасибо за объяснение того, почему два списка нельзя считать одинаковыми.
Райли Ларк,
Система типов Java делает вид, что массивы ковариантны, но на самом деле они не могут быть заменены, что подтверждается исключением ArrayStoreException.
Паоло Эберманн 08
38

Эриксон уже объяснил, почему вы не можете этого сделать, но вот несколько решений:

Если вы хотите только удалить элементы из вашего базового списка, в принципе, ваш метод получения должен быть объявлен как принимающий List<? extends BaseClass>.

Но если это не так и вы не можете его изменить, вы можете обернуть список Collections.unmodifiableList(...), что позволяет вернуть List супертипа параметра аргумента. (Это позволяет избежать проблемы безопасности типов, выбрасывая исключение UnsupportedOperationException при попытках вставки.)

Пауло Эберманн
источник
Большой! не знал этого.
keuleJ 06
Большое спасибо!
Воланд
14

Как объяснил @erickson, если вам действительно нужна ссылка на исходный список, убедитесь, что никакой код не вставляет ничего в этот список, если вы когда-нибудь захотите снова использовать его в исходном объявлении. Самый простой способ получить его - просто привести его к простому старому неуниверсальному списку:

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

Я бы не рекомендовал это, если вы не знаете, что происходит со списком, и предлагал бы вам изменить любой код, который нужен для принятия списка, который у вас есть.

hd42
источник
3

Ниже приведен полезный фрагмент, который работает. Он создает новый список массивов, но создание объекта JVM без дополнительных затрат не имеет значения.

Я видел, что другие ответы необязательно сложные.

List<BaseClass> baselist = new ArrayList<>(sublist);
sigirisetti
источник
1
Спасибо за этот фрагмент кода, который может сразу помочь. Правильное объяснение значительно повысило бы его образовательную ценность, показав, почему это хорошее решение проблемы, и сделало бы его более полезным для будущих читателей, задающих похожие, но не идентичные вопросы. Пожалуйста, отредактируйте свой ответ, чтобы добавить объяснение и указать, какие ограничения и предположения применяются. В частности, чем он отличается от кода в вопросе, который создает новый список?
Тоби Спейт
Накладные расходы не являются незначительными, поскольку они также должны (неглубоко) копировать каждую ссылку. Таким образом, он масштабируется с размером списка, поэтому это операция O (n).
john16384
2

Я пропустил ответ, в котором вы просто приводили исходный список с помощью двойного преобразования. Итак, для полноты:

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

Ничего не копируется, операция выполняется быстро. Однако здесь вы обманываете компилятор, поэтому вы должны быть абсолютно уверены, что не изменили список таким образом, чтобы в subListначале содержались элементы другого подтипа. При работе с неизменяемыми списками это обычно не проблема.

john16384
источник
1

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

jtahlborn
источник
5
Это не должно работать, так как для этих методов все аргументы и результат имеют один и тот же параметр типа.
Паоло Эберманн,
странно, ты прав. по какой-то причине у меня сложилось впечатление, что этот метод был полезен для улучшения общей коллекции. можно по-прежнему использовать таким образом, и он обеспечит «безопасную» коллекцию, если вы захотите использовать подавление предупреждений.
jtahlborn
1

То, что вы пытаетесь сделать, очень полезно, и я считаю, что мне нужно делать это очень часто в коде, который я пишу. Пример использования:

Скажем, у нас есть интерфейс, Fooи у нас есть zorkingпакет, ZorkingFooManagerкоторый создает и управляет экземплярами package-private.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;
}

Не знаю, зачем нужен clazz в Eclipse ..

olivervbk
источник
0

Как насчет литья всех элементов. Он создаст новый список, но будет ссылаться на исходные объекты из старого списка.

List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());
drordk
источник
Это полный фрагмент кода, который работает для преобразования списка подклассов в суперкласс.
kanaparthikiran
0

Это полный рабочий фрагмент кода, использующий Generics, для преобразования списка подклассов в суперкласс.

Вызывающий метод, который передает тип подкласса

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

Метод Callee, который принимает любой подтип базового класса

private static List<String> getElementDefinitionStatuses(List<? extends 
    BaseClass> baseClassVariableName) {
     return allElementStatuses;
    }
}
kanaparthikiran
источник