Załóżmy na przykład, że masz dwie klasy:
public class TestA {}
public class TestB extends TestA{}
Mam metodę, która zwraca a List<TestA>
i chciałbym rzutować wszystkie obiekty z tej listy na, TestB
aby skończyć z List<TestB>
.
Załóżmy na przykład, że masz dwie klasy:
public class TestA {}
public class TestB extends TestA{}
Mam metodę, która zwraca a List<TestA>
i chciałbym rzutować wszystkie obiekty z tej listy na, TestB
aby skończyć z List<TestB>
.
Odpowiedzi:
Po prostu casting do List<TestB>
prawie działa; ale to nie działa, ponieważ nie można rzutować typu ogólnego jednego parametru na inny. Możesz jednak użyć pośredniego typu symbolu wieloznacznego i będzie to dozwolone (ponieważ możesz przesyłać do i z typów symboli wieloznacznych, tylko z niezaznaczonym ostrzeżeniem):
List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
rzutowanie ogólnych nie jest możliwe, ale jeśli zdefiniujesz listę w inny sposób, możesz w niej zapisać TestB:
List<? extends TestA> myList = new ArrayList<TestA>();
Nadal masz sprawdzanie typu do zrobienia, gdy używasz obiektów z listy.
Cannot instantiate the type ArrayList<? extends TestA>
.
Naprawdę nie możesz *:
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 prawidłowy, ponieważ B
jest podklasą A
. Co się teraz dzieje z List<A>
i List<B>
?
Okazuje się, że List<B>
nie jest to podklasa, List<A>
dlatego nie możemy pisać
List<B> b = new ArrayList<>();
List<A> a = b; // error, List<B> is not of type List<A>
Co więcej, nie możemy nawet pisać
List<B> b = new ArrayList<>();
List<A> a = (List<A>)b; // error, List<B> is not of type List<A>
*: Aby umożliwić casting, potrzebujemy wspólnego rodzica dla obu List<A>
i List<B>
: List<?>
na przykład. Poniższa jest ważny:
List<B> b = new ArrayList<>();
List<?> t = (List<B>)b;
List<A> a = (List<A>)t;
Otrzymasz jednak ostrzeżenie. Możesz go wyłączyć, dodając @SuppressWarnings("unchecked")
do swojej metody.
Z Javą 8 możesz to zrobić
List<TestB> variable = collectionOfListA
.stream()
.map(e -> (TestB) e)
.collect(Collectors.toList());
new List<TestB>
pętlą i obsadą?
Ponieważ jest to pytanie, na które często się powołuje, a obecne odpowiedzi głównie wyjaśniają, dlaczego to nie działa (lub proponują hackerskie, niebezpieczne rozwiązania, których nigdy nie chciałbym widzieć w kodzie produkcyjnym), uważam, że należy dodać kolejną odpowiedź, pokazującą pułapki i możliwe rozwiązanie.
Powód, dla którego to ogólnie nie działa, został już wskazany w innych odpowiedziach: To, czy konwersja jest rzeczywiście ważna, zależy od typów obiektów zawartych na oryginalnej liście. Gdy istnieją obiekty w liście, którego typ nie jest typu TestB
, ale o innej podklasy TestA
, następnie obsada jest nie ważna.
Oczywiście, obsady mogą być ważne. Czasami masz informacje o typach, które nie są dostępne dla kompilatora. W takich przypadkach możliwe jest przesyłanie list, ale ogólnie nie jest zalecane :
Można albo ...
Implikacje pierwszego podejścia (które odpowiadają obecnie przyjętej odpowiedzi) są subtelne. Na pierwszy rzut oka może się wydawać, że działa poprawnie. Ale jeśli na liście wejściowej znajdują się niewłaściwe typy, to ClassCastException
zostanie wyrzucony, być może w zupełnie innym miejscu w kodzie, i może być trudno go debugować i dowiedzieć się, gdzie niewłaściwy element wślizgnął się na listę. Najgorszym problemem jest to, że ktoś może nawet dodać nieprawidłowe elementy po rzutowaniu listy, co jeszcze bardziej utrudnia debugowanie.
Problem debugowania tych fałszywych ClassCastExceptions
można złagodzić dzięki Collections#checkedCollection
rodzinie metod.
Bardziej bezpiecznym sposobem na konwersję z a List<Supertype>
na a List<Subtype>
jest przefiltrowanie listy i utworzenie nowej listy zawierającej tylko elementy określonego typu. Istnieją pewne stopnie swobody we wdrażaniu takiej metody (np. W odniesieniu do traktowania null
wpisów), ale jedna możliwa implementacja może wyglądać następująco:
/**
* 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;
}
Metodę tę można zastosować do filtrowania dowolnych list (nie tylko z daną relacją podtypu i typu pod względem 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]
Nie można rzucać List<TestB>
się List<TestA>
jak wspomina Steve Kuo ale można zrzucić zawartość List<TestA>
do List<TestB>
. Spróbuj wykonać następujące czynności:
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ą to błędy, ale pomysł polega na tym, że powinien on iterować przez obiekt danych dodając elementy (obiekty TestB) do listy. Mam nadzieję, że ci się uda.
Najlepszym bezpiecznym sposobem jest zaimplementowanie AbstractList
i rzucenie przedmiotów w trakcie implementacji. Stworzyłem ListUtil
klasę pomocnika:
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ć cast
metody ślepego rzutowania obiektów na liście oraz convert
metody bezpiecznego rzutowania. 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
}
Jedyny sposób, jaki znam to kopiowanie:
List<TestB> list = new ArrayList<TestB> (
Arrays.asList (
testAList.toArray(new TestB[0])
)
);
Kiedy rzutujesz odwołanie do obiektu, po prostu rzutujesz typ odwołania, a nie typ obiektu. rzutowanie nie zmieni faktycznego typu obiektu.
Java nie ma niejawnych 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 szczegółowe niż w języku z bezpośrednim wsparciem, ale działa i nie powinieneś robić tego zbyt często.
jeśli masz obiekt klasy TestA
, nie możesz go rzucić TestB
. każdy TestB
jest TestA
, ale nie na odwrót.
w następującym kodzie:
TestA a = new TestA();
TestB b = (TestB) a;
druga linia rzuciłaby ClassCastException
.
możesz rzucić TestA
odwołanie tylko wtedy, gdy sam obiekt jest TestB
. na przykład:
TestA a = new TestB();
TestB b = (TestB) a;
więc nie zawsze możesz rzucić listę TestA
na listę TestB
.
Możesz użyć tej selectInstances
metody w kolekcji Eclipse . Będzie to wiązało się z utworzeniem nowej kolekcji, jednak nie będzie tak wydajne, jak zaakceptowane rozwiązanie wykorzystujące rzutowanie.
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);
Podał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 osobą odpowiedzialną za kolekcje Eclipse.
Jest to możliwe z powodu usunięcia 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żesz rzucać jednego na drugi - nie ma nic do rzucenia.
Integer j = 42; Integer i = (Integer) j;
działa dobrze, oba są liczbami całkowitymi, oba mają tę samą klasę, więc „nie ma co rzucać”.
Problem polega na tym, że twoja metoda NIE zwraca listy TestA, jeśli zawiera TestB, więc co, jeśli została poprawnie wpisana? Następnie ta obsada:
class TestA{};
class TestB extends TestA{};
List<? extends TestA> listA;
List<TestB> listB = (List<TestB>) listA;
działa tak dobrze, jak można się spodziewać (Eclipse ostrzega przed niesprawdzoną obsadą, która jest dokładnie tym, co robisz, więc meh). Czy możesz to wykorzystać do rozwiązania swojego problemu? W rzeczywistości 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 co prosiłeś, prawda? lub być naprawdę dokładnym:
List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist;
wydaje się, że dobrze się kompiluje.
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 przesyłać List<Object>
na List<MyClass>
. Ale musisz zwrócić uwagę na to, że objectList
musi zawierać instancje tego samego typu co MyClass
. I ten przykład można rozważyć, gdy List<T>
jest używany. W tym celu pobierz pole Class<T> clazz
w konstruktorze i użyj go zamiast MyClass.class
.
Dość dziwne, że ręczne przesyłanie listy wciąż nie jest dostępne w niektórych narzędziach implementujących coś takiego:
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends E, E> List<T> cast(List<E> list) {
return (List) list;
}
Oczywiście nie będzie to sprawdzać pozycji jeden po drugim, ale właśnie tego chcemy tutaj uniknąć, jeśli dobrze wiemy, że nasza implementacja zapewnia tylko podtyp.