Самый эффективный способ приведения списка <SubClass>к списку <BaseClass> - PullRequest
120 голосов
/ 22 февраля 2011

У меня есть List<SubClass>, который я хочу рассматривать как List<BaseClass>. Кажется, что это не должно быть проблемой, поскольку приведение SubClass к BaseClass совсем несложно, но мой компилятор жалуется, что приведение невозможно.

Итак, как лучше всего получить ссылку на те же объекты, что и List<BaseClass>?

Сейчас я просто создаю новый список и копирую старый список:

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

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

Ответы [ 9 ]

158 голосов
/ 22 февраля 2011

Синтаксис для этого вида назначения использует подстановочный знак:

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 будет возбуждено в некотором отдаленном, трудно ассоциируемом месте в коде, когда значение будет считано из коллекции. Если вы учитываете предупреждения компилятора о безопасности типов, вы избежите этих ошибок типов во время выполнения.

35 голосов
/ 23 февраля 2011

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

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

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

10 голосов
/ 22 февраля 2011

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

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

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

1 голос
/ 19 июля 2017

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

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

List<BaseClass> baselist = new ArrayList<>(sublist);
1 голос
/ 06 апреля 2017

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

Скажем, у нас есть интерфейс Foo, и у нас есть пакет zorking, который имеет ZorkingFooManager, который создает и управляет экземплярами package-private ZorkingFoo implements Foo.(Очень распространенный сценарий.)

Итак, ZorkingFooManager должен содержать private Collection<ZorkingFoo> zorkingFoos, но он должен предоставлять public Collection<Foo> getAllFoos().

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

Решением этой проблемы, конечно же, является уменьшение коллекции.Вот лучший способ сделать это:

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, очевидно, можно использовать его в присваивании, поэтому дополнительная переменная «result» решает эту проблему.(В любом случае он должен быть оптимизирован либо компилятором, либо JIT.)

1 голос
/ 22 февраля 2011

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

0 голосов
/ 05 сентября 2018

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

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

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

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

private static List<String> getElementDefinitionStatuses(List<? extends 
    BaseClass> baseClassVariableName) {
     return allElementStatuses;
    }
}
0 голосов
/ 27 мая 2018

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

List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());
0 голосов
/ 26 марта 2015

Примерно так же должно работать:

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 ..

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...