Jak rzutować List <Object> na List <MyClass>

Odpowiedzi:

157

zawsze możesz rzutować dowolny obiekt na dowolny typ, przesyłając go najpierw do Object. w Twoim przypadku:

(List<Customer>)(Object)list; 

musisz mieć pewność, że w czasie wykonywania lista zawiera tylko obiekty Customer.

Krytycy twierdzą, że takie rzutowanie wskazuje na coś złego w twoim kodzie; powinieneś być w stanie dostosować swoje deklaracje typu, aby tego uniknąć. Ale generics Java jest zbyt skomplikowany i nie jest doskonały. Czasami po prostu nie wiesz, czy istnieje ładne rozwiązanie, które zadowoli kompilatora, mimo że bardzo dobrze znasz typy środowiska uruchomieniowego i wiesz, że to, co próbujesz zrobić, jest bezpieczne. W takim przypadku po prostu wykonaj surowy odlew w razie potrzeby, abyś mógł wyjść z pracy do domu.

niepodważalny
źródło
8
To też potrzebuje @SuppressWarnings("unchecked"). Zauważ, że możesz także przesyłać do (List)zamiast do (Object).
200_success
36

Dzieje się tak, ponieważ chociaż klient jest obiektem , lista klientów nie jest listą obiektów. Gdyby tak było, to można by umieścić dowolny obiekt na liście Klientów.

Brian Agnew
źródło
2
Nie, nie jest. Ominąłbyś bezpieczeństwo typu, gdyby powyższe było dozwolone. Zauważ, że przesyłanie nie oznacza tworzenia nowej listy i kopiowania elementów. Oznacza to obsługę pojedynczej instancji jako innego typu, a zatem masz listę zawierającą potencjalnie obiekty inne niż Customer z gwarancją bezpieczeństwa typu, której nie powinien. Nie ma to nic wspólnego z java jako taką. Ponieważ czujesz, że ta funkcja sprawia, że ​​Java utknęła w mrocznych czasach, wzywam Cię do wyjaśnienia, jak Twoim zdaniem powinno to działać.
Lasse V. Karlsen
1
@ BrainSlugs83 dla twoich potrzeb (List <Customer>) (Object) list;
Muthu Ganapathy Nathan
@ LasseV.Karlsen Nie mówię o castingu, mówię o konwersji. Nie obejdzie bezpieczeństwa typu, wymusi to. Implementacja typów ogólnych w Javie, brak przeciążeń operatorów, metod rozszerzających i wielu innych nowoczesnych udogodnień sprawiły, że jestem niesamowicie rozczarowany. - W międzyczasie .NET ma do tego dwie oddzielne metody rozszerzeń - jedną wywołaną .Cast<T>()i drugą wywołaną .OfType<T>(). Pierwsza z nich wykonuje rzut na każdym elemencie (rzucając pożądane wyjątki), podczas gdy druga odfiltrowuje elementy, których nie można rzucić (więc wybierz jeden w zależności od scenariusza użycia).
BrainSlugs83
1
@EAGER_STUDENT Nie pomijałbym Javy, która może faktycznie działać (cała ta absurdalna firma wymazywania typów, musiałbym spróbować ...) - ale nie, nigdy nie napisałbym takiego kodu - przegrywasz bezpieczeństwo typów, co się stanie, jeśli jeden element kolekcji nie jest instanceofKlientem?
BrainSlugs83
1
@ BrainSlugs83 Osoba, która zadała pytanie, zapytała konkretnie o casting. A jeśli Java nie ma jeszcze odpowiednich metod, takich jak metody .NET, do których się odwołujesz (które nadal nie konwertują btw), powinieneś być w stanie je łatwo dodać. To wszystko jest jednak trochę ortogonalne w stosunku do omawianego pytania, które dotyczyło konkretnej składni.
Lasse V. Karlsen
35

W zależności od innego kodu najlepsza odpowiedź może się różnić. Próbować:

List<? extends Object> list = getList();
return (List<Customer>) list;

lub

List list = getList();
return (List<Customer>) list;

Pamiętaj jednak, że nie zaleca się wykonywania takich niekontrolowanych rzutów.

Bozho
źródło
3
-1 - to naprawdę zły nawyk i jeśli nie użyjesz adnotacji „Unchecked”, kompilator nadal będzie na to narzekał
kdgregory
24

Ze strumieniami Java 8 :

Czasami rzucanie siłą jest w porządku:

List<MyClass> mythings = (List<MyClass>) (Object) objects

Ale oto bardziej wszechstronne rozwiązanie:

List<Object> objects = Arrays.asList("String1", "String2");

List<String> strings = objects.stream()
                       .map(element->(String) element)
                       .collect(Collectors.toList());

Jest mnóstwo korzyści, ale jedną z nich jest to, że możesz bardziej elegancko przesyłać swoją listę, jeśli nie masz pewności, co zawiera:

objects.stream()
    .filter(element->element instanceof String)
    .map(element->(String)element)
    .collect(Collectors.toList());
okrągły
źródło
1
To jednak bardziej kopia niż obsada.
200_success
Casting wymieniony w zaakceptowanej odpowiedzi nie działał dla mnie. Byłem też na Javie 7. Ale Guava FluentIterablena mnie zadziałała.
Sridhar Sarnobat
To jest to, czego szukałem, po prostu nie znałem składni
Fueled By Coffee
23

Możesz użyć podwójnej obsady.

return (List<Customer>) (List) getList();
Peter Lawrey
źródło
8

Zauważ, że nie jestem programistą Java, ale w .NET i C # ta funkcja nazywa się kontrawariancją lub kowariancją. Nie zagłębiłem się jeszcze w te rzeczy, ponieważ są one nowe w .NET 4.0, którego nie używam, ponieważ jest to tylko beta, więc nie wiem, który z dwóch terminów opisuje twój problem, ale pozwól mi opisać problem techniczny z tym.

Załóżmy, że pozwolono ci rzucać. Zwróć uwagę, mówię rzutuj , ponieważ to właśnie powiedziałeś, ale są dwie operacje, które mogą być możliwe, rzutowanie i konwersja .

Konwersja oznaczałaby, że otrzymujesz nowy obiekt listy, ale mówisz, że rzutowanie, co oznacza, że ​​chcesz tymczasowo traktować jeden obiekt jako inny typ.

Oto problem z tym.

Co by się stało, gdyby było dozwolone (uwaga, zakładam, że przed rzutem lista obiektów faktycznie zawiera tylko obiekty klienta, w przeciwnym razie rzut nie działałby nawet w tej hipotetycznej wersji java):

List<Object> list = getList();
List<Customer> customers = (List<Customer>)list;
list.Insert(0, new someOtherObjectNotACustomer());
Customer c = customers[0];

W takim przypadku spowoduje to próbę potraktowania obiektu, który nie jest klientem, jako klienta, aw pewnym momencie wystąpi błąd w czasie wykonywania, albo z formularza wewnątrz listy, albo z przypisania.

Jednak typy generyczne mają zapewniać typy danych bezpieczne dla typów, takie jak kolekcje, a ponieważ lubią rzucać słowo „gwarantowane”, tego rodzaju rzutowanie wraz z następującymi problemami jest niedozwolone.

W .NET 4.0 (wiem, twoje pytanie dotyczyło javy), będzie to dozwolone w bardzo szczególnych przypadkach , w których kompilator może zagwarantować, że wykonywane operacje są bezpieczne, ale w ogólnym sensie ten typ rzutowania nie będzie mieć pozwolenie. To samo odnosi się do języka Java, chociaż nie jestem pewien co do planów wprowadzenia współ- i kontrawariancji do języka Java.

Mam nadzieję, że ktoś z lepszą znajomością języka Java niż ja może opowiedzieć o szczegółach dotyczących przyszłości lub implementacji Java.

Lasse V. Karlsen
źródło
3
Przyszłością Javy jest ... Scala. Poważnie, facet, który umieścił typy generyczne w Javie, opracował nowy język, który jest nieco podobny do Javy, ale naprawdę bardzo biegły w typach: bardzo dokładna i spójna implementacja obsługi typów. Myślę, że nikt nie wie na pewno, które funkcje Scali powrócą do Javy i kiedy.
Carl Smotricz
doskonałe wyjaśnienie kowariancji, które naprawdę odpowiada na pytanie dotyczące PO. Dobra robota.
Dzień Kevina
Carl: Myślałem, że niektórzy programiści Java zaczęli tworzyć C #? :) Zresztą tak, Java najprawdopodobniej zmierza w przyszłości w kierunku Scali zamiast, powiedzmy, czegoś mniej silnie wpisanego.
Esko
@Carl - w Scali istnieje subtelna różnica w tym, że domyślnie listy są niezmienne. Więc generalnie nie masz problemu z dodaniem obiektu do listy klientów, ponieważ kiedy to zrobisz, otrzymasz nową listę obiektów .
Brian Agnew
Eee ... technicznie jest to poprawne - ale nawet przed .NET 4.0 - można to zrobić za pomocą zwykłych metod rozszerzeń IEnumerable (.Cast <> i .OfType <>) - więc nie ma potrzeby schodzenia z głębokiego końca, jeśli chcesz po prostu silnej iteracji typu.
BrainSlugs83
7

Innym podejściem byłoby użycie strumienia Java 8.

    List<Customer> customer = myObjects.stream()
                                  .filter(Customer.class::isInstance)
                                  .map(Customer.class::cast)
                                  .collect(toList());
d0x
źródło
1
dziękuję, to rozwiązanie jest tak piękne, stosując metody referencyjnej
thang
1
Nigdy nie myślałem, że ktoś przewinie tak daleko: D
d0x
3

Powinieneś po prostu powtórzyć listę i rzucić wszystkie Obiekty jeden po drugim

vrm
źródło
3

Możesz zrobić coś takiego

List<Customer> cusList = new ArrayList<Customer>();

for(Object o: list){        
    cusList.add((Customer)o);        
}

return cusList; 

Lub sposób Java 8

list.stream().forEach(x->cusList.add((Customer)x))

return cuslist;
Malkeith Singh
źródło
2

Nie możesz, ponieważ List<Object>i List<Customer>nie są w tym samym drzewie dziedziczenia.

Możesz dodać nowy konstruktor do swojej List<Customer>klasy, który pobiera a, List<Object>a następnie iterować po liście, rzutując każdy Objectna a Customeri dodając go do swojej kolekcji. Należy pamiętać, że nieprawidłowy wyjątek rzutowania może wystąpić, jeśli obiekt wywołujący List<Object>zawiera coś, co nie jest plikiem Customer.

Celem list ogólnych jest ograniczenie ich do określonych typów. Próbujesz wziąć listę, która może zawierać cokolwiek (zamówienia, produkty itp.) I wcisnąć ją do listy, która może zawierać tylko klientów.

Rob Sobers
źródło
2

Możesz stworzyć nową Listę i dodać do niej elementy:

Na przykład:

List<A> a = getListOfA();
List<Object> newList = new ArrayList<>();
newList.addAll(a);
ayushgp
źródło
1

Najlepszym rozwiązaniem jest utworzenie nowego List<Customer>, iteracyjne przeglądanie List<Object>, dodanie każdej pozycji do nowej listy i zwrócenie jej.

Aric TenEyck
źródło
1

Jak zauważyli inni, nie możesz ich bezpiecznie rzucać, ponieważ a List<Object>nie jest List<Customer>. To, co możesz zrobić, to zdefiniować widok na liście, który sprawdza typ w miejscu. Korzystając z Google Kolekcje , byłoby to:

return Lists.transform(list, new Function<Object, Customer>() {
  public Customer apply(Object from) {
    if (from instanceof Customer) {
      return (Customer)from;
    }
    return null; // or throw an exception, or do something else that makes sense.
  }
});
nd.
źródło
1

Podobnie z Bozho powyżej. Możesz obejść ten problem tutaj (chociaż mi się to nie podoba) za pomocą tej metody:

public <T> List<T> convert(List list, T t){
    return list;
}

Tak. Spowoduje to rzutowanie listy na wymagany typ ogólny.

W podanym powyżej przypadku możesz wykonać taki kod:

    List<Object> list = getList();
    return convert(list, new Customer());
Hendra Jaya
źródło
Podoba mi się to rozwiązanie. Nawet jeśli musisz dodać SuppressWarnings, lepiej jest dodać go w jednym miejscu niż przy każdym niebezpiecznym rzucaniu.
robson
1

W zależności od tego, co chcesz zrobić z listą, może nawet nie być konieczne przesyłanie jej do pliku List<Customer>. Jeśli chcesz tylko dodać Customerobiekty do listy, możesz zadeklarować to w następujący sposób:

...
List<Object> list = getList();
return (List<? super Customer>) list;

Jest to legalne (no cóż, nie tylko legalne, ale poprawne - lista jest „jakiegoś nadtypu dla Klienta”) i jeśli zamierzasz przekazać ją do metody, która będzie jedynie dodawać obiekty do listy, to powyższe wystarczą do tego ogólne granice.

Z drugiej strony, jeśli chcesz pobrać obiekty z listy i mieć je silnie wpisane jako Klienci - to nie masz szczęścia i słusznie. Ponieważ lista jest listą, List<Object>nie ma gwarancji, że zawartość jest klientem, więc będziesz musiał zapewnić własny rzut przy pobieraniu. (Lub bądź naprawdę, absolutnie, podwójnie pewny, że lista będzie zawierać Customersi używać tylko podwójnego rzutu z jednej z pozostałych odpowiedzi, ale zdaj sobie sprawę, że całkowicie omijasz bezpieczeństwo typów w czasie kompilacji, które otrzymujesz od typów ogólnych w tym walizka).

Mówiąc ogólnie, zawsze dobrze jest wziąć pod uwagę najszersze możliwe ogólne granice, które byłyby dopuszczalne podczas pisania metody, podwójnie, jeśli ma być używana jako metoda biblioteczna. Jeśli masz zamiar czytać tylko z listy, użyj List<? extends T>zamiast List<T>, na przykład - daje to dzwoniącym znacznie większy zakres argumentów, które mogą przekazać, i oznacza, że ​​jest mniej prawdopodobne, że napotkają możliwe do uniknięcia problemy podobne do tego, mam tutaj.

Andrzej Doyle
źródło