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

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

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