Наприклад, скажімо, що у вас є два класи:
public class TestA {}
public class TestB extends TestA{}
У мене є метод, який повертає a, List<TestA>
і я хотів би передати всі об'єкти в цьому списку TestB
так, щоб я закінчив "a" List<TestB>
.
Наприклад, скажімо, що у вас є два класи:
public class TestA {}
public class TestB extends TestA{}
У мене є метод, який повертає a, List<TestA>
і я хотів би передати всі об'єкти в цьому списку TestB
так, щоб я закінчив "a" List<TestB>
.
Відповіді:
Просто кастинг List<TestB>
майже працює; але це не працює, оскільки ви не можете передавати загальний тип одного параметра іншому. Однак ви можете передати через проміжний тип підстановки, і це буде дозволено (оскільки ви можете передавати на типи та з підстановочних типів, лише з неперевіреним попередженням):
List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
кастинг генеричних файлів неможливий, але якщо ви визначите список іншим способом, можна зберігати в ньому TestB:
List<? extends TestA> myList = new ArrayList<TestA>();
Ви все ще маєте перевірити тип, коли ви використовуєте об'єкти зі списку.
Ви дійсно не можете *:
Приклад взято з цього підручника 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")
до свого методу.
З Java 8 ви насправді можете
List<TestB> variable = collectionOfListA
.stream()
.map(e -> (TestB) e)
.collect(Collectors.toList());
new List<TestB>
, циклом та кастом?
Оскільки це широко посилається на запитання, а нинішні відповіді в основному пояснюють, чому це не працює (або пропонуються хакі, небезпечні рішення, які я ніколи не хотів би бачити у виробничому коді), я вважаю за доцільне додати ще одну відповідь, показуючи підводні камені та можливе рішення.
Причина, по якій це не працює в цілому, вже вказано в інших відповідях: чи дійсно конвертація дійсна, залежить від типів об'єктів, що містяться в початковому списку. Якщо в списку є об'єкти, тип яких не тип TestB
, а інший підклас TestA
, тоді команда не є дійсною.
Звичайно, касти можуть бути дійсними. Іноді у вас є інформація про типи, які недоступні для компілятора. У цих випадках списки можна робити, але загалом це не рекомендується :
Можна було б ...
Наслідки першого підходу (який відповідає прийнятій на даний момент відповідь) тонкі. На перший погляд це може здатися, що він працює належним чином. Але якщо в списку вводу є неправильні типи, тоді ClassCastException
буде викинуто а, можливо, зовсім інше місце в коді, і це може бути важко налагодити це і з'ясувати, де неправильний елемент прослизнув у список. Найгірша проблема полягає в тому, що хтось може навіть додати недійсні елементи після кастингу списку, що робить налагодження ще складніше.
Проблема налагодження цих помилкових ClassCastExceptions
може бути усунена Collections#checkedCollection
сімейством методів.
Більш безпечний для типу спосіб перетворення від a List<Supertype>
до a 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;
}
Цей метод може бути використаний для фільтрації довільних списків (не тільки із заданим співвідношенням підтип-супертип щодо параметрів типу), як у цьому прикладі:
// 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]
Ви не можете кинути , List<TestB>
щоб List<TestA>
Стів Kuo згадує , але ви можете скинути вміст List<TestA>
в List<TestB>
. Спробуйте наступне:
List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);
Я не пробував цей код, тому, ймовірно, є помилки, але ідея полягає в тому, що він повинен повторюватися через об'єкт даних, додаючи елементи (TestB-об'єкти) до Списку. Я сподіваюся, що це працює для вас.
Найкращий безпечний спосіб - це реалізація 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
}
Єдине, що мені відомо, це копіювання:
List<TestB> list = new ArrayList<TestB> (
Arrays.asList (
testAList.toArray(new TestB[0])
)
);
Коли ви передаєте посилання на об'єкт, ви просто видаєте тип посилання, а не тип об'єкта. кастинг не змінить фактичний тип об'єкта.
У 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));
}
Це більш багатослівно, ніж у мові з прямою підтримкою, але це працює, і вам не потрібно робити це дуже часто.
якщо у вас є об'єкт класу TestA
, ви не можете його надати TestB
. кожен TestB
- aTestA
, але не інакше.
у наступному коді:
TestA a = new TestA();
TestB b = (TestB) a;
другий рядок буде кидати a ClassCastException
.
ви можете подати TestA
посилання лише в тому випадку, якщо є сам об'єкт TestB
. наприклад:
TestA a = new TestB();
TestB b = (TestB) a;
Таким чином, ви не завжди можете додавати список TestA
до списку TestB
.
Ви можете використовувати selectInstances
метод у колекціях Eclipse . Це передбачає створення нової колекції, однак це не буде настільки ефективним, як прийняте рішення, яке використовує кастинг.
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.
Це можливо завдяки стирання типу. Ви знайдете це
List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true
Внутрішньо обидва списки мають тип List<Object>
. З цієї причини ви не можете кидати одне одному - нічого не можна кидати.
Integer j = 42; Integer i = (Integer) j;
працює чудово, обидва цілі числа, вони обоє мають один клас, тому "не можна нічого кидати".
Проблема полягає в тому, що ваш метод НЕ повертає список TestA, якщо він містить TestB, і що робити, якщо він був правильно введений? Тоді цей акторський склад:
class TestA{};
class TestB extends TestA{};
List<? extends TestA> listA;
List<TestB> listB = (List<TestB>) listA;
працює так само добре, як ви могли сподіватися (Eclipse попереджає вас про неперевірений акторський склад, який саме ви робите, так що мені). Тож чи можете ви використовувати це для вирішення своєї проблеми? Насправді ви можете через це:
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;
здається, складати для мене просто чудово.
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
.
Це має працювати
List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));
Досить дивно, що вручну введення списку все ще не надається ящиком інструментів, що реалізує щось на кшталт:
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends E, E> List<T> cast(List<E> list) {
return (List) list;
}
Звичайно, це не буде перевіряти елементи один за одним, але саме цього ми хочемо уникати, якщо ми добре знаємо, що наша реалізація надає лише підтип.