Как вы перечислите список супертипов в список подтипов?
Например, скажем, у вас есть два класса:
public class TestA {}
public class TestB extends TestA{}
У меня есть метод, который возвращает List<TestA>
, и я хотел бы передать все объекты из этого списка в TestB
, чтобы в итоге я получил List<TestB>
.
Ответы
Ответ 1
Простое нажатие на List<TestB>
работает почти; но это не работает, потому что вы не можете использовать общий тип одного параметра для другого. Тем не менее, вы можете пропустить промежуточный тип подстановочного знака, и он будет разрешен (поскольку вы можете использовать типы подстановочных знаков и без них, просто с непроверенным предупреждением):
List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
Ответ 2
приведение дженериков невозможно, но если вы определите список по-другому, в нем можно сохранить TestB:
List<? extends TestA> myList = new ArrayList<TestA>();
У вас все еще есть проверка типов при использовании объектов в списке.
Ответ 3
Вы действительно не можете *:
Пример взят из этого руководства по Java
Предположим, есть два типа A
и B
, такие как B extends A
.
Тогда следующий код верен:
B b = new B();
A a = b;
Предыдущий код действителен, поскольку B
является подклассом A
.
Теперь, что происходит с List<A>
и List<B>
?
Оказывается, что List<B>
не является подклассом List<A>
, поэтому мы не можем написать
List<B> b = new ArrayList<>();
List<A> a = b; // error, List<B> is not of type List<A>
Кроме того, мы не можем даже написать
List<B> b = new ArrayList<>();
List<A> a = (List<A>)b; // error, List<B> is not of type List<A>
*: чтобы сделать возможным кастинг, нам нужен общий родительский элемент для List<A>
и List<B>
: например, List<?>
. Следующее действительно :
List<B> b = new ArrayList<>();
List<?> t = (List<B>)b;
List<A> a = (List<A>)t;
Однако вы получите предупреждение. Вы можете подавить это, добавив @SuppressWarnings("unchecked")
к вашему методу.
Ответ 4
С Java 8 вы действительно можете
List<TestB> variable = collectionOfListA
.stream()
.map(e -> (TestB) e)
.collect(Collectors.toList());
Ответ 5
Я думаю, что вы кажетесь в неправильном направлении, хотя... если метод возвращает список объектов TestA
, тогда действительно небезопасно отбрасывать их на TestB
.
В основном вы просите компилятор разрешить вам выполнять операции TestB
для типа TestA
, который их не поддерживает.
Ответ 6
Поскольку это широко упоминаемый вопрос, и текущие ответы в основном объясняют, почему он не работает (или предлагать хакерские, опасные решения, которые я никогда не хотел бы видеть в производственном коде), я думаю, что это уместно добавить еще один ответ, показать подводные камни и возможное решение.
Причина, по которой это не работает вообще, уже упоминалось в других ответах: действительно ли преобразование действительно действительно зависит от типов объектов, которые содержатся в исходном списке. Когда в списке есть объекты, тип которых не имеет тип TestB
, но другого подкласса TestA
, то приведение недействительно.
Конечно, броски могут быть действительными. Иногда у вас есть информация о типах, недоступных для компилятора. В этих случаях можно создавать списки, но в целом не рекомендуется:
Можно либо...
- ... введите весь список или
- ... лить все элементы списка
Последствия первого подхода (который соответствует принятому в настоящее время ответам) являются тонкими. Казалось бы, это сработало правильно с первого взгляда. Но если в списке входных данных есть неправильные типы, тогда будет выбрано значение ClassCastException
, возможно, в совершенно другом месте в коде, и может быть сложно отладить это и выяснить, где в список попал неправильный элемент, Худшая проблема заключается в том, что кто-то может даже добавить недопустимые элементы после того, как список был запущен, что еще более затрудняет отладку.
Проблема отладки этих ложных ClassCastExceptions
может быть облегчена с помощью Collections#checkedCollection
методов.
Фильтрация списка на основе типа
Более безопасный тип преобразования из List<Supertype>
в List<Subtype>
состоит в том, чтобы фактически фильтровать список и создать новый список, содержащий только элементы с определенным типом. Существуют некоторые степени свободы для реализации такого метода (например, в отношении обработки записей null
), но одна возможная реализация может выглядеть так:
/**
* Filter the given list, and create a new list that only contains
* the elements that are (subtypes) of the class c
*
* @param listA The input list
* @param c The class to filter for
* @return The filtered list
*/
private static <T> List<T> filter(List<?> listA, Class<T> c)
{
List<T> listB = new ArrayList<T>();
for (Object a : listA)
{
if (c.isInstance(a))
{
listB.add(c.cast(a));
}
}
return listB;
}
Этот метод может использоваться для фильтрации произвольных списков (не только с данным отношением типа Subtype-Supertype относительно параметров типа), как в этом примере:
// A list of type "List<Number>" that actually
// contains Integer, Double and Float values
List<Number> mixedNumbers =
new ArrayList<Number>(Arrays.asList(12, 3.4, 5.6f, 78));
// Filter the list, and create a list that contains
// only the Integer values:
List<Integer> integers = filter(mixedNumbers, Integer.class);
System.out.println(integers); // Prints [12, 78]
Ответ 7
Вы не можете отбрасывать List<TestB>
в List<TestA>
, как говорит Стив Куо, но вы можете сбросить содержимое List<TestA>
в List<TestB>
. Попробуйте следующее:
List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);
Я не пробовал этот код, поэтому есть ошибки, но идея состоит в том, что он должен перебирать объект данных, добавляя в него элементы (объекты TestB). Я надеюсь, что это сработает для вас.
Ответ 8
Лучшим безопасным способом является реализация AbstractList
и литых элементов в реализации. Я создал ListUtil
вспомогательный класс:
public class ListUtil
{
public static <TCastTo, TCastFrom extends TCastTo> List<TCastTo> convert(final List<TCastFrom> list)
{
return new AbstractList<TCastTo>() {
@Override
public TCastTo get(int i)
{
return list.get(i);
}
@Override
public int size()
{
return list.size();
}
};
}
public static <TCastTo, TCastFrom> List<TCastTo> cast(final List<TCastFrom> list)
{
return new AbstractList<TCastTo>() {
@Override
public TCastTo get(int i)
{
return (TCastTo)list.get(i);
}
@Override
public int size()
{
return list.size();
}
};
}
}
Вы можете использовать метод cast
для слепого литья объектов в списке и метода convert
для безопасного литья.
Пример:
void test(List<TestA> listA, List<TestB> listB)
{
List<TestB> castedB = ListUtil.cast(listA); // all items are blindly casted
List<TestB> convertedB = ListUtil.<TestB, TestA>convert(listA); // wrong cause TestA does not extend TestB
List<TestA> convertedA = ListUtil.<TestA, TestB>convert(listB); // OK all items are safely casted
}
Ответ 9
Когда вы передаете ссылку на объект, вы просто вводите тип ссылки, а не тип объекта. кастинг не изменит фактический тип объекта.
Java не имеет неявных правил для преобразования типов объектов. (В отличие от примитивов)
Вместо этого вам нужно предоставить способ преобразования одного типа в другой и вызвать его вручную.
public class TestA {}
public class TestB extends TestA{
TestB(TestA testA) {
// build a TestB from a TestA
}
}
List<TestA> result = ....
List<TestB> data = new List<TestB>();
for(TestA testA : result) {
data.add(new TestB(testA));
}
Это более подробный, чем на языке с прямой поддержкой, но он работает, и вам не нужно делать это очень часто.
Ответ 10
Единственное, что я знаю, это копирование:
List<TestB> list = new ArrayList<TestB> (
Arrays.asList (
testAList.toArray(new TestB[0])
)
);
Ответ 11
Это возможно из-за стирания типа. Вы обнаружите, что
List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true
Внутренне оба списка имеют тип List<Object>
. По этой причине вы не можете отбрасывать один на другой - ничего не делать.
Ответ 12
если у вас есть объект класса TestA
, его нельзя отнести к TestB
. каждый TestB
является TestA
, но не другим.
в следующем коде:
TestA a = new TestA();
TestB b = (TestB) a;
вторая строка выкинет ClassCastException
.
вы можете использовать только ссылку TestA
, если сам объект TestB
. например:
TestA a = new TestB();
TestB b = (TestB) a;
поэтому вы не всегда можете перечислить список TestA
в список TestB
.
Ответ 13
Проблема заключается в том, что ваш метод НЕ возвращает список TestA, если он содержит TestB, так что, если он был правильно напечатан? Затем этот актер:
class TestA{};
class TestB extends TestA{};
List<? extends TestA> listA;
List<TestB> listB = (List<TestB>) listA;
работает так же хорошо, как вы могли бы надеяться (Eclipse предупреждает вас о неконтролируемом акте, который именно вы делаете, поэтому meh). Так вы можете использовать это, чтобы решить свою проблему? На самом деле вы можете из-за этого:
List<TestA> badlist = null; // Actually contains TestBs, as specified
List<? extends TestA> talist = badlist; // Umm, works
List<TextB> tblist = (List<TestB>)talist; // TADA!
Именно то, что вы просили, не так ли? или быть действительно точным:
List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist;
кажется, компилируется для меня отлично.
Ответ 14
class MyClass {
String field;
MyClass(String field) {
this.field = field;
}
}
@Test
public void testTypeCast() {
List<Object> objectList = Arrays.asList(new MyClass("1"), new MyClass("2"));
Class<MyClass> clazz = MyClass.class;
List<MyClass> myClassList = objectList.stream()
.map(clazz::cast)
.collect(Collectors.toList());
assertEquals(objectList.size(), myClassList.size());
assertEquals(objectList, myClassList);
}
Этот тест показывает, как привести List<Object>
к List<MyClass>
. Но вам нужно обратить внимание на то, что objectList
должен содержать экземпляры того же типа, что и MyClass
. И этот пример можно рассмотреть при использовании List<T>
. Для этого возьмите поле Class<T> clazz
в конструкторе и используйте его вместо MyClass.class
.
Ответ 15
Вы можете использовать метод selectInstances
в Eclipse Collections. Однако это потребует создания новой коллекции, поэтому она не будет столь же эффективной, как принятое решение, использующее кастинг.
List<CharSequence> parent =
Arrays.asList("1","2","3", new StringBuffer("4"));
List<String> strings =
Lists.adapt(parent).selectInstancesOf(String.class);
Assert.assertEquals(Arrays.asList("1","2","3"), strings);
Я включил StringBuffer
в пример, чтобы показать, что selectInstances
не только понижает тип, но и фильтрует, если коллекция содержит смешанные типы.
Примечание: я являюсь коммиттером для Eclipse Collections.
Ответ 16
Это должно работать
List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));