Самый эффективный способ листинга списка <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);