Rozważ ten przykład (typowy w książkach OOP):
Mam Animal
zajęcia, w których każdy Animal
może mieć wielu przyjaciół.
I podklasy podoba Dog
, Duck
, Mouse
itp, które jak dodać specyficzne zachowanie bark()
, quack()
itd.
Oto Animal
klasa:
public class Animal {
private Map<String,Animal> friends = new HashMap<>();
public void addFriend(String name, Animal animal){
friends.put(name,animal);
}
public Animal callFriend(String name){
return friends.get(name);
}
}
A oto fragment kodu z dużą ilością rzutowania:
Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());
((Dog) jerry.callFriend("spike")).bark();
((Duck) jerry.callFriend("quacker")).quack();
Czy jest jakiś sposób, w jaki mogę użyć rodzajów ogólnych dla typu zwracanego, aby pozbyć się rzutowania, aby to powiedzieć
jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();
Oto kod początkowy z typem zwracanym przekazywanym do metody jako parametr, który nigdy nie jest używany.
public<T extends Animal> T callFriend(String name, T unusedTypeObj){
return (T)friends.get(name);
}
Czy istnieje sposób na określenie typu zwrotu w czasie wykonywania bez użycia dodatkowego parametru instanceof
? Lub przynajmniej przekazując klasę typu zamiast fałszywej instancji.
Rozumiem, że ogólne są przeznaczone do sprawdzania typu czasu kompilacji, ale czy istnieje na to obejście?
źródło
Nie. Kompilator nie może wiedzieć, jaki typ
jerry.callFriend("spike")
zwróci. Ponadto twoja implementacja po prostu ukrywa obsadę w metodzie bez żadnego dodatkowego bezpieczeństwa typu. Rozważ to:W tym konkretnym przypadku utworzenie
talk()
metody abstrakcyjnej i odpowiednie jej zastąpienie w podklasach przydałoby się znacznie lepiej:źródło
Możesz to zaimplementować w następujący sposób:
(Tak, to jest legalny kod; patrz Java Generics: Typ ogólny zdefiniowany tylko jako typ zwracany ).
Typ zwrotu zostanie wyprowadzony z dzwoniącego. Zwróć jednak uwagę na
@SuppressWarnings
adnotację: która mówi, że ten kod nie jest bezpieczny . Musisz to zweryfikować samodzielnie, lub możesz uzyskaćClassCastExceptions
w czasie wykonywania.Niestety, sposób, w jaki go używasz (bez przypisywania wartości zwracanej do zmiennej tymczasowej), jedynym sposobem na zadowolenie kompilatora jest wywołanie go w następujący sposób:
Chociaż może to być nieco ładniejsze niż casting, prawdopodobnie lepiej jest dać
Animal
klasie abstrakcyjnątalk()
metodę, jak powiedział David Schmitt.źródło
jerry.CallFriend<Dog>(...
wydaje mi się, że wygląda lepiej.java.util.Collections.emptyList()
funkcja środowiska JRE jest implementowana dokładnie w ten sposób, a jego javadoc reklamuje się jako bezpieczny.To pytanie jest bardzo podobne do punktu 29 w Effective Java - „Rozważ bezpieczne typy heterogenicznych pojemników”. Odpowiedź Laz jest najbliższa rozwiązaniu Blocha. Jednak zarówno put, jak i get powinny używać literału Class dla bezpieczeństwa. Podpisy stałyby się:
Wewnątrz obu metod należy sprawdzić, czy parametry są rozsądne. Aby uzyskać więcej informacji, zobacz Efektywna Java i javadoc klasy .
źródło
Dodatkowo możesz poprosić metodę o zwrócenie wartości w danym typie w ten sposób
Więcej przykładów tutaj w dokumentacji Oracle Java
źródło
Oto prostsza wersja:
W pełni działający kod:
źródło
Casting to T not needed in this case but it's a good practice to do
. Mam na myśli to, że jeśli jest właściwie załatwione w czasie wykonywania, co oznacza „dobra praktyka”?Jak powiedziałeś, zaliczenie zajęć byłoby OK, możesz napisać to:
A następnie użyj tego w ten sposób:
Nie jest to idealne rozwiązanie, ale jest tak daleko, jak to możliwe w przypadku generycznych programów Java. Istnieje sposób na zaimplementowanie kontenerów heterogenicznych Typesafe (THC) przy użyciu tokenów supertypowych , ale to znowu ma swoje problemy.
źródło
Opierając się na tym samym pomyśle, co tokeny supertypowe, możesz utworzyć identyfikator maszynowy zamiast łańcucha:
Ale myślę, że może to pokonać cel, ponieważ musisz teraz utworzyć nowe obiekty id dla każdego łańcucha i trzymać się ich (lub zrekonstruować je z poprawnymi informacjami o typie).
Ale możesz teraz używać tej klasy tak, jak pierwotnie chciałeś, bez rzutów.
To po prostu ukrywa parametr typu wewnątrz identyfikatora, chociaż oznacza to, że możesz później pobrać typ z identyfikatora, jeśli chcesz.
Musisz także zaimplementować metody porównywania i mieszania TypedID, jeśli chcesz mieć możliwość porównania dwóch identycznych instancji identyfikatora.
źródło
„Czy istnieje sposób na określenie typu zwrotu w czasie wykonywania bez dodatkowego parametru za pomocą instanceof?”
Jako alternatywne rozwiązanie można wykorzystać taki wzór użytkownika . Stwórz streszczenie Animal i spraw, by wdrożył:
Odwiedzalny oznacza po prostu, że realizacja Animal jest gotowa przyjąć gościa:
A implementacja odwiedzającego może odwiedzić wszystkie podklasy zwierzęcia:
Na przykład implementacja Dog wyglądałaby następująco:
Sztuczka polega na tym, że ponieważ pies wie, jaki to typ, może uruchomić odpowiednią przeciążoną metodę wizyty użytkownika v, przekazując parametr „this”. Inne podklasy implementowałyby accept () dokładnie w ten sam sposób.
Klasa, która chce wywołać metody specyficzne dla podklasy, musi następnie zaimplementować interfejs Visitor w następujący sposób:
Wiem, że jest o wiele więcej interfejsów i metod, niż się spodziewałeś, ale jest to standardowy sposób, aby uzyskać kontrolę nad każdym konkretnym podtypem z dokładnie zerowym sprawdzaniem instancji i rzutowaniem typu zero. Wszystko to odbywa się w sposób agnostyczny w standardowym języku, więc nie jest to tylko Java, ale każdy język OO powinien działać tak samo.
źródło
Niemożliwe. Skąd Mapa ma wiedzieć, jaką podklasę Zwierząt otrzyma, mając tylko klucz String?
Jedynym sposobem byłoby to możliwe, gdyby każde zwierzę zaakceptowało tylko jeden typ przyjaciela (wówczas może to być parametr klasy Animal) lub metoda callFriend () otrzyma parametr typu. Ale tak naprawdę wygląda na to, że brakuje Ci punktu dziedziczenia: chodzi o to, że podklasy można traktować jednolicie tylko przy użyciu wyłącznie metod nadklasy.
źródło
Napisałem artykuł, który zawiera dowód koncepcji, klasy wsparcia i klasę testową, która pokazuje, w jaki sposób tokeny supertypowe mogą być pobierane przez twoje klasy w czasie wykonywania. Krótko mówiąc, umożliwia delegowanie do alternatywnych implementacji w zależności od rzeczywistych parametrów ogólnych przekazywanych przez program wywołujący. Przykład:
TimeSeries<Double>
deleguje się do prywatnej klasy wewnętrznej, która używadouble[]
TimeSeries<OHLC>
deleguje się do prywatnej klasy wewnętrznej, która używaArrayList<OHLC>
Zobacz: Używanie TypeTokens do pobierania ogólnych parametrów
Dzięki
Richard Gomes - Blog
źródło
Jest tu wiele świetnych odpowiedzi, ale takie podejście zastosowałem do testu Appium, w którym działanie na jednym elemencie może skutkować przejściem do różnych stanów aplikacji w zależności od ustawień użytkownika. Chociaż nie jest zgodny z konwencjami z przykładu OP, mam nadzieję, że komuś pomoże.
Jeśli nie chcesz wyrzucać błędów, możesz je złapać w następujący sposób:
źródło
Nie bardzo, ponieważ, jak mówisz, kompilator wie tylko, że callFriend () zwraca zwierzę, a nie psa lub kaczkę.
Czy nie możesz dodać abstrakcyjnej metody makeNoise () do Animal, która byłaby zaimplementowana jako kora lub szarlatan za pomocą jej podklas?
źródło
To, czego tu szukasz, to abstrakcja. Koduj więcej przeciwko interfejsom i powinieneś robić mniej castingu.
Poniższy przykład jest w języku C #, ale koncepcja pozostaje taka sama.
źródło
W moim lib kontraktorze wykonałem następujące czynności:
podklasowanie:
przynajmniej działa to w bieżącej klasie i przy silnym typowaniu odwołania. Wielokrotne dziedziczenie działa, ale wtedy staje się naprawdę trudne :)
źródło
Wiem, że to zupełnie inna sprawa niż ta, o którą pytał. Innym sposobem rozwiązania tego byłoby odbicie. Mam na myśli, że nie czerpie to korzyści z Generics, ale pozwala w pewien sposób naśladować zachowanie, które chcesz wykonać (zrobić szczekanie psa, kwakanie itp.) Bez konieczności rzucania rzutami:
źródło
co powiesz na
}
źródło
Istnieje inne podejście, możesz zawęzić typ zwracany, gdy zastąpisz metodę. W każdej podklasie należy zastąpić funkcję callFriend, aby zwrócić tę podklasę. Kosztem byłoby wielokrotne deklaracje callFriend, ale można wyodrębnić wspólne części metody zwanej wewnętrznie. Wydaje mi się to o wiele prostsze niż rozwiązania wspomniane powyżej i nie wymaga dodatkowego argumentu, aby określić typ zwrotu.
źródło
public int getValue(String name){}
jest nie do odróżnienia zpublic boolean getValue(String name){}
punktu widzenia kompilatorów. Aby rozpoznać przeciążenie, trzeba zmienić typ parametru lub dodać / usunąć parametry. Może po prostu cię źle zrozumiałem ...Możesz zwrócić dowolny typ i otrzymać bezpośrednio jak. Nie trzeba wybierać tekstu.
Jest to najlepsze, jeśli chcesz dostosować dowolny inny rodzaj rekordów zamiast prawdziwych kursorów.
źródło
(Cursor) cursor
na przykład.cursor
być aCursor
i zawsze zwróciPerson
(lub null). Użycie generics usuwa sprawdzanie tych ograniczeń, czyniąc kod niebezpiecznym.