Как вы приведете список супертипов к списку подтипов?


244

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

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

У меня есть метод, который возвращает a, List<TestA>и я хотел бы привести все объекты в этом списке, TestBчтобы в итоге я получил 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
Это позволяет мне писать: List <String> myList = (List <String>) (List <?>) (New ArrayList <Integer> ()); Это может произойти сбой во время выполнения. Я предпочитаю что-то вроде List <? extends Number> myList = new ArrayList <Integer> ();
Бентайе

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

@ jfritz42 это функционально не отличается от приведения объекта к
целому

116

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

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

У вас все еще есть проверка типов, когда вы используете объекты в списке.


14
Это даже не правильный синтаксис Java.
newacct

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

У меня есть следующий метод: createSingleString (List <? Extends Object> objects) Внутри этого метода я вызываю String.valueOf (Object), чтобы свернуть список в одну строку. Он отлично работает, когда вводим List <Long>, List <Boolean> и т. Д. Спасибо!
Крис Спраг

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
Кроме того, использование материалов по этой ссылке может избежать непроверенного преобразования, поскольку компилятор может расшифровать все преобразование.
Райан Х

29

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

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

32
Это на самом деле составление списка? Потому что это больше похоже на серию операций для итерации по всему списку, приведения каждого элемента и последующего добавления их во вновь созданный список нужного типа. Иными словами, не могли бы вы сделать именно это с Java7 - с new List<TestB>помощью цикла, приведения и приведения?
Патрик М

5
Из ОП «и я хотел бы разыграть все объекты в этом списке» - так что на самом деле это один из немногих ответов, который отвечает на поставленный вопрос, даже если это не тот вопрос, который просматривают здесь люди после.
Люк Ашервуд

17

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

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


11

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


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


Конечно, приведения могут быть действительными. Иногда у вас есть информация о типах, которые недоступны для компилятора. В этих случаях можно привести списки, но в общем случае это не рекомендуется :

Можно или ...

  • ... бросить весь список или
  • ... бросить все элементы списка

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

Проблема отладки этих ложных 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) в список. Я надеюсь, что это работает для вас.


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

7
Конечно, этот пост не является неправильным. Но человеку, который задал вопрос, наконец-то нужен список TestB. В этом случае этот ответ вводит в заблуждение. Вы можете добавить все элементы из List <TestB> в List <TestA>, вызвав List <TestA> .addAll (List <TestB>). Но вы не можете использовать List <TestB> .addAll (List <TestA>);
пражет

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-массивы так бесполезны. Но я думаю, что это, конечно, не хуже и намного читабельнее, чем другие ответы. Я не вижу в этом ничего более опасного, чем актеры.
Thufir

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есть TestA, но не наоборот.

в следующем коде:

TestA a = new TestA();
TestB b = (TestB) a;

вторая строка будет бросать ClassCastException.

Вы можете привести TestAссылку только если сам объект TestB. например:

TestA a = new TestB();
TestB b = (TestB) a;

Таким образом, вы не всегда можете привести список TestAк списку TestB.


1
Я думаю, что он говорит о чем-то вроде: вы получаете «a» в качестве аргумента метода, который создается как - TestA a = new TestB (), затем вы хотите получить объект TestB, а затем вам нужно привести его - TestB b = ( TestB) a;
Самсамара

2

Вы можете использовать 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.


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
Это делает ненужную копию.
defnull

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.