Jak dodać listę supertypów do listy podtypów?

Na przykład, powiedzmy, że masz dwie klasy:

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

Mam metodę, która zwraca List<TestA> i chciałbym wrzucić wszystkie obiekty z tej listy do TestB, aby skończyć z List<TestB>.

Author: Paul Bellora, 2009-06-01

16 answers

Po prostu rzut do List<TestB> prawie działa; ale to nie działa, ponieważ nie można rzucić ogólny typ jednego parametru do drugiego. Można jednak rzucać przez pośredni Typ wildcard i będzie to dozwolone (ponieważ można rzucać do i z typów wildcard, tylko z niezaznaczonym ostrzeżeniem): {]}

List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
 503
Author: newacct,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-15 10:04:58

Rzutowanie generyków nie jest możliwe, ale jeśli zdefiniujesz listę w inny sposób, możliwe jest zapisanie w niej TestB:

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

Nadal musisz sprawdzać tekst, gdy używasz obiektów na liście.

 102
Author: Salandur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-18 17:01:48

You really can ' t*:

Przykład pochodzi z tego samouczka Java

Załóżmy, że istnieją dwa typy A i B takie, że B extends A. Następnie następujący kod jest poprawny:

    B b = new B();
    A a = b;

Poprzedni kod jest ważny, ponieważ B jest podklasą A. Co się dzieje z List<A> i List<B>?

Okazuje się, że List<B> nie jest podklasą List<A> dlatego nie możemy napisać

    List<B> b = new ArrayList<>();
    List<A> a = b; // error, List<B> is not of type List<A>

Ponadto, my nie możemy nawet napisz

    List<B> b = new ArrayList<>();
    List<A> a = (List<A>)b; // error, List<B> is not of type List<A>

*: aby casting był możliwy potrzebujemy wspólnego rodzica zarówno dla List<A> i List<B>: List<?> na przykład. Następujące jest Ważne:

    List<B> b = new ArrayList<>();
    List<?> t = (List<B>)b;
    List<A> a = (List<A>)t;
Jednak otrzymasz ostrzeżenie. Możesz go stłumić, dodając @SuppressWarnings("unchecked") do swojej metody.
 37
Author: Steve Kuo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-06 11:29:48

Z Javą 8, faktycznie można

List<TestB> variable = collectionOfListA
    .stream()
    .map(e -> (TestB) e)
    .collect(Collectors.toList());
 25
Author: fracz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-10 10:08:56

Myślę, że rzucasz w złym kierunku... jeśli metoda zwraca listę obiektów TestA, to nie jest bezpieczne ich wrzucenie do TestB.

Zasadniczo prosisz kompilator, aby pozwolił ci wykonywać operacje TestB na typie TestA, który ich nie obsługuje.

 17
Author: jerryjvl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-01 03:30:02

ponieważ jest to szeroko odwołane pytanie, a obecne odpowiedzi głównie wyjaśniają, dlaczego nie działa (lub proponują hacky, niebezpieczne rozwiązania, które nigdy nie chciałbym zobaczyć w kodzie produkcyjnym), myślę, że należy dodać kolejną odpowiedź, pokazując pułapki i możliwe rozwiązanie.


Powód, dla którego to nie działa w ogóle, został już wymieniony w innych odpowiedziach: to, czy konwersja jest rzeczywiście ważna, zależy od typów obiektów znajdujących się na oryginalnej liście. Jeśli na liście znajdują się obiekty, których Typ nie jest typu TestB, ale innej podklasy TestA, to cast jest Nie poprawny.


Oczywiście, odlewy mogą być ważne. Czasami masz informacje o typach, które nie są dostępne dla kompilatora. W takich przypadkach możliwe jest rzucenie list, ale ogólnie jest to nie zalecane :

Można ani jedno, ani drugie...

  • ... cast the whole list or
  • ... wrzuć wszystkie elementy listy

Implikacje pierwszego podejścia (które odpowiada obecnie akceptowanej odpowiedzi) są subtelne. Na pierwszy rzut oka może się wydawać, że działa prawidłowo. Ale jeśli na liście wejściowej są złe typy, to ClassCastException zostanie wyrzucony, być może w zupełnie innym miejscu w kodzie i może być trudno to debugować i dowiedzieć się, gdzie niewłaściwy element wsunął się do lista. Najgorszym problemem jest to, że ktoś może nawet dodać nieprawidłowe elementy po lista została rzucona, co utrudnia debugowanie.

Problem debugowania tych fałszywych ClassCastExceptions można złagodzić za pomocą Collections#checkedCollection rodzina metod.


Filtrowanie listy na podstawie typu

Bardziej bezpiecznym sposobem konwersji z List<Supertype> na List<Subtype> jest faktycznie filtrowanie i tworzenie nowej listy, która zawiera tylko elementy, które mają określony typ. Istnieją pewne stopnie swobody w implementacji takiej metody( np. w odniesieniu do traktowania wpisów null), ale jedna możliwa implementacja może wyglądać tak:

/**
 * 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;
}

Ta metoda może być użyta do filtrowania dowolnych List (nie tylko z daną relacją Podtyp-Supertype dotyczącą parametrów typu), jak w tym przykładzie:

// 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
Author: Marco13,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-26 16:59:12

Najlepszym bezpiecznym sposobem jest zaimplementowanie AbstractList i rzucenie elementów w implementację. Stworzyłem ListUtil klasę pomocniczą:

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

Możesz użyć metody cast do ślepego rzucania obiektów w liście i metody convert do bezpiecznego rzucania. Przykład:

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
Author: Igor Zubchenok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-02 12:14:00

Nie możesz wrzucić List<TestB> do List<TestA>, Jak wspomina Steve Kuo, ale możesz wrzucić zawartość List<TestA> do List<TestB>. Spróbuj:

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

Nie próbowałem tego kodu, więc prawdopodobnie są błędy, ale chodzi o to, że powinien on iterować poprzez obiekt data dodając elementy (Obiekty TestB) do listy. Mam nadzieję, że ci to pasuje.

 5
Author: martinatime,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-01 04:16:22

Kiedy rzucasz odniesienie do obiektu, rzucasz tylko Typ odniesienia, a nie typ obiektu. odlewanie nie zmieni rzeczywistego typu obiektu.

Java nie ma ukrytych reguł konwersji typów obiektów. (W przeciwieństwie do prymitywów)

Zamiast tego musisz podać, jak przekonwertować jeden typ na inny i wywołać go ręcznie.

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

Jest to bardziej słowne niż w języku z bezpośrednim wsparciem, ale działa i nie powinieneś tego robić bardzo często.

 3
Author: Peter Lawrey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-01 05:58:42

Jedyny sposób jaki znam to kopiowanie:

List<TestB> list = new ArrayList<TestB> (
    Arrays.asList (
        testAList.toArray(new TestB[0])
    )
);
 3
Author: Peter Heusch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-20 08:35:05

Jest to możliwe dzięki kasowaniu typu. Znajdziesz to

List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true

Wewnętrznie obie listy są typu List<Object>. Z tego powodu nie można rzucać jednego do drugiego - nie ma co rzucać.

 2
Author: n3rd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-01 03:30:06

Jeśli masz obiekt klasy TestA, nie możesz go oddać do TestB. każdy TestB jest TestA, ale nie w drugą stronę.

W następującym kodzie:

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

Druga linia rzuci ClassCastException.

Możesz rzucić odniesienie TestA tylko wtedy, gdy sam obiekt jest TestB. na przykład:

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

Więc nie zawsze możesz wrzucić listę TestA do listy TestB.

 2
Author: cd1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-01 03:31:54

Problem polega na tym, że twoja metoda nie zwraca listy TestA, jeśli zawiera TestB, co z tego, że została poprawnie wpisana? Wtedy ta Obsada:

class TestA{};
class TestB extends TestA{};
List<? extends TestA> listA;
List<TestB> listB = (List<TestB>) listA;

Działa tak dobrze, jak mogłeś mieć nadzieję (Eclipse ostrzega Cię o niezaangażowanej obsadzie, która jest dokładnie tym, co robisz, więc meh). Możesz użyć tego, by rozwiązać swój problem? Właściwie możesz z tego powodu:

List<TestA> badlist = null; // Actually contains TestBs, as specified
List<? extends TestA> talist = badlist;  // Umm, works
List<TextB> tblist = (List<TestB>)talist; // TADA!
Dokładnie o to prosiłeś, prawda? a dokładniej:
List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist;

Wydaje się, że kompiluje się dobrze dla ja.

 1
Author: Bill K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-22 18:09:12
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);
}

Ten test pokazuje, jak rzucić List<Object> do List<MyClass>. Należy jednak zwrócić uwagę na to, że {[3] } musi zawierać instancje tego samego typu co MyClass. I ten przykład można wziąć pod uwagę przy użyciu List<T>. W tym celu Pobierz pole Class<T> clazz w konstruktorze i użyj go zamiast MyClass.class.

 1
Author: Alex Po,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-27 07:57:43

To powinno zadziałać

List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));
 0
Author: anonymouse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-14 16:06:58

Możesz użyć metody selectInstances w kolekcjach Eclipse . Będzie to wymagało stworzenia nowej kolekcji, jednak nie będzie tak skuteczne, jak przyjęte rozwiązanie wykorzystujące odlewanie.

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

Dodałem StringBuffer w przykładzie, aby pokazać, że selectInstances nie tylko obniża Typ, ale także filtruje, jeśli Kolekcja zawiera typy mieszane.

Uwaga: jestem committerem Dla Kolekcji Eclipse.

 0
Author: Donald Raab,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-28 18:33:59