Самый эффективный способ листинга списка <SubClass> для списка <BaseClass>

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

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

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

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

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

Ответ 1

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

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

Важно понимать, что a List<SubClass> является не взаимозаменяемым с List<BaseClass>. Код, который сохраняет ссылку на List<SubClass>, ожидает, что каждый элемент в списке будет SubClass. Если другая часть кода относится к списку как List<BaseClass>, компилятор не будет жаловаться при вставке BaseClass или AnotherSubClass. Но это приведет к ClassCastException для первого фрагмента кода, который предполагает, что все в списке является SubClass.

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

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

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

Итак, ZorkingFooManager должен содержать private Collection<ZorkingFoo> zorkingFoos, но ему нужно выставить a 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, по-видимому, прекрасно использовать его при назначении, поэтому дополнительная переменная "result" решает эту проблему. (Он должен быть оптимизирован либо компилятором, либо JIT в любом случае.)

Ответ 6

Что-то вроде этого тоже должно работать:

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

Ответ 7

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

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

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