Как вы приведете список супертипов к списку подтипов? - PullRequest
219 голосов
/ 01 июня 2009

Например, допустим, у вас есть два класса:

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

У меня есть метод, который возвращает List<TestA>, и я хотел бы привести все объекты в этом списке к TestB, чтобы я получил List<TestB>.

Ответы [ 16 ]

540 голосов
/ 01 июня 2009

Просто приведение к List<TestB> почти работает; но это не работает, потому что вы не можете привести общий тип одного параметра к другому. Тем не менее, вы можете использовать промежуточный тип подстановочного знака, и это будет разрешено (поскольку вы можете приводить к типам подстановочных знаков и обратно, просто с непроверенным предупреждением):

List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
110 голосов
/ 01 июня 2009

Приведение дженериков невозможно, но если вы определите список другим способом, в нем можно сохранить TestB:

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

У вас по-прежнему есть проверка типов при использовании объектов в списке.

39 голосов
/ 01 июня 2009

Вы действительно не можете *:

Пример взят из этого руководства по 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") к вашему методу.

27 голосов
/ 10 февраля 2015

С Java 8 вы действительно можете

List<TestB> variable = collectionOfListA
    .stream()
    .map(e -> (TestB) e)
    .collect(Collectors.toList());
18 голосов
/ 01 июня 2009

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

По сути, вы просите компилятор позволить вам TestB выполнять операции с типом TestA, который их не поддерживает.

8 голосов
/ 26 мая 2015

Поскольку на этот вопрос часто ссылаются, и текущие ответы в основном объясняют, почему он не работает (или предлагают хакерские, опасные решения, которые мне никогда не хотелось бы видеть в рабочем коде), я думаю, что это уместно добавить еще один ответ, показывая подводные камни и возможное решение.


Причина, по которой это не работает в целом, уже указана в других ответах: действительно ли преобразование действительно выполняется, зависит от типов объектов, которые содержатся в исходном списке. Если в списке есть объекты, тип которых не относится к типу 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;
}

Этот метод может использоваться для фильтрации произвольных списков (не только с заданным отношением Подтип-Супертип относительно параметров типа), как в этом примере:

// 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]
6 голосов
/ 02 марта 2013

Лучший безопасный способ - реализовать 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
}
6 голосов
/ 01 июня 2009

Вы не можете привести List<TestB> к List<TestA>, как Стив Куо упоминает, НО вы можете выбросить содержимое List<TestA> в List<TestB>. Попробуйте следующее:

List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);

Я не пробовал этот код, поэтому, вероятно, есть ошибки, но идея в том, что он должен перебирать объект данных, добавляя элементы (объекты TestB) в список. Я надеюсь, что это работает для вас.

3 голосов
/ 20 сентября 2011

Единственный способ, которым я знаю, это скопировать:

List<TestB> list = new ArrayList<TestB> (
    Arrays.asList (
        testAList.toArray(new TestB[0])
    )
);
3 голосов
/ 01 июня 2009

Когда вы приводите ссылку на объект, вы просто приводите тип ссылки, а не тип объекта. приведение не изменит фактический тип объекта.

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

Это более многословно, чем в языке с прямой поддержкой, но это работает, и вам не нужно делать это очень часто.

...