Як можна занести список супертипів до списку підтипів?


244

Наприклад, скажімо, що у вас є два класи:

public class TestA {}
public class TestB extends TestA{}

У мене є метод, який повертає a, List<TestA>і я хотів би передати всі об'єкти в цьому списку TestBтак, щоб я закінчив "a" List<TestB>.

Відповіді:


578

Просто кастинг List<TestB>майже працює; але це не працює, оскільки ви не можете передавати загальний тип одного параметра іншому. Однак ви можете передати через проміжний тип підстановки, і це буде дозволено (оскільки ви можете передавати на типи та з підстановочних типів, лише з неперевіреним попередженням):

List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;

88
З усією повагою я думаю, що це якесь хакітне рішення - ви ухиляєтесь від типу безпеки, яку намагається вам забезпечити Java. Принаймні подивіться цей підручник Java ( docs.oracle.com/javase/tutorial/java/generics/subtyping.html ) і подумайте, чому у вас виникає ця проблема, перш ніж виправити її таким чином.
jfritz42

63
@ jfritz42 Я б не назвав це "ухиленням" від безпеки типу. У цьому випадку у вас є знання, яких у Java немає. Ось для чого кастинг.
Планки

3
Це дозволяє мені написати: Список <String> myList = (Список <String>) (Список <?>) (Новий ArrayList <Integer> ()); Це може вийти з ладу під час виконання. Я віддаю перевагу щось на зразок Список <? розширює Number> myList = new ArrayList <Integer> ();
Bentaye

1
Я чесно погоджуюся з @ jfritz42 У мене виникло те саме питання, і я переглянув URL, який він надав, і міг вирішити мою проблему без кастингу.
Сем Орозько

@ jfritz42 це функціонально не відрізняється від викидання об'єкта до цілого числа, що дозволено компілятором
kcazllerraf

116

кастинг генеричних файлів неможливий, але якщо ви визначите список іншим способом, можна зберігати в ньому TestB:

List<? extends TestA> myList = new ArrayList<TestA>();

Ви все ще маєте перевірити тип, коли ви використовуєте об'єкти зі списку.


14
Це не дійсний синтаксис Java.
newacct

2
Це правда, але це було більш
наочно,

У мене є такий спосіб: createSingleString (Список <? Розширює об'єкт> об'єкти) Всередині цього методу я викликаю String.valueOf (Object), щоб згортати список в одну рядок. Він чудово працює, коли введення списку <Довго>, Список <Булева> тощо. Дякую!
Кріс Спрагей

2
Що робити, якщо TestA є інтерфейсом?
Сувітруф - Андрій Апанасик

8
Чи можете ви дати MCVE, де це насправді працює? Я отримую Cannot instantiate the type ArrayList<? extends TestA>.
Nateowami

42

Ви дійсно не можете *:

Приклад взято з цього підручника 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")до свого методу.


2
Також, використовуючи матеріал у цьому посиланні, можна уникнути неперевіреної конверсії, оскільки компілятор може розшифрувати всю конверсію.
Ryan H

29

З Java 8 ви насправді можете

List<TestB> variable = collectionOfListA
    .stream()
    .map(e -> (TestB) e)
    .collect(Collectors.toList());

32
Це насправді кастинг списку? Оскільки він читає більше, як серію операцій, щоб повторити весь список, перекинувши кожен елемент, а потім додавши їх до нещодавно створеного списку потрібного типу. Інакше кажучи, чи не могли ви зробити це саме з Java7- з a new List<TestB>, циклом та кастом?
Патрік М

5
З ОП "і я хотів би віддати всі об'єкти в цей список" - так, насправді, це одна з небагатьох відповідей, яка відповідає на запитання, як це було заявлено, навіть якщо це не питання, яке люди переглядають тут.
Люк Ушервуд

17

Я думаю, що ти кидаєш у неправильному напрямку, хоча ... якщо метод повертає список TestAоб'єктів, тоді його не можна безпечно передатиTestB .

В основному ви просите компілятор дозволити виконувати TestBоперації типу TestA, який їх не підтримує.


11

Оскільки це широко посилається на запитання, а нинішні відповіді в основному пояснюють, чому це не працює (або пропонуються хакі, небезпечні рішення, які я ніколи не хотів би бачити у виробничому коді), я вважаю за доцільне додати ще одну відповідь, показуючи підводні камені та можливе рішення.


Причина, по якій це не працює в цілому, вже вказано в інших відповідях: чи дійсно конвертація дійсна, залежить від типів об'єктів, що містяться в початковому списку. Якщо в списку є об'єкти, тип яких не тип 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]

7

Ви не можете кинути , 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-об'єкти) до Списку. Я сподіваюся, що це працює для вас.


чому це було проголосовано? Мені це було корисно, і я не бачу пояснень для тих, хто проголосував.
zod

7
Безумовно, ця публікація є невірною. Але людині, яка поставила запитання, нарешті потрібен список TestB. У цьому випадку ця відповідь вводить в оману. Ви можете додати всі елементи зі списку <TestB> до списку <TestA>, зателефонувавши до списку <TestA> .addAll (Список <TestB>). Але ви не можете використовувати List <TestB> .addAll (Список <TestA>);
prageeth

6

Найкращий безпечний спосіб - це реалізація 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
}

4

Єдине, що мені відомо, це копіювання:

List<TestB> list = new ArrayList<TestB> (
    Arrays.asList (
        testAList.toArray(new TestB[0])
    )
);

2
Дуже дивно, я думаю, що це не дає попередження компілятору.
Саймон Форсберг

це дивацтво з масивами. Зітхайте, масиви Java настільки марні. Але, я думаю, це, звичайно, не гірше і читабельніше, ніж інші відповіді. Я не бачу цього більш небезпечним, ніж акторський склад.
Туфір

3

Коли ви передаєте посилання на об'єкт, ви просто видаєте тип посилання, а не тип об'єкта. кастинг не змінить фактичний тип об'єкта.

У 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));
}

Це більш багатослівно, ніж у мові з прямою підтримкою, але це працює, і вам не потрібно робити це дуже часто.


2

якщо у вас є об'єкт класу 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.


1
Я думаю, що він говорить про щось на кшталт, ви отримуєте "a" як аргумент методу, який створюється як - TestA a = новий TestB (), тоді ви хочете отримати об'єкт TestB, а потім вам потрібно його накинути - TestB b = ( TestB) a;
самсамара

2

Ви можете використовувати 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.


1

Це можливо завдяки стирання типу. Ви знайдете це

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;працює чудово, обидва цілі числа, вони обоє мають один клас, тому "не можна нічого кидати".
Саймон Форсберг

1

Проблема полягає в тому, що ваш метод НЕ повертає список 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;

здається, складати для мене просто чудово.


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


0

Це має працювати

List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));

1
Це робить непотрібну копію.
деблокувати

0

Досить дивно, що вручну введення списку все ще не надається ящиком інструментів, що реалізує щось на кшталт:

@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends E, E> List<T> cast(List<E> list) {
    return (List) list;
}

Звичайно, це не буде перевіряти елементи один за одним, але саме цього ми хочемо уникати, якщо ми добре знаємо, що наша реалізація надає лише підтип.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.