Wydawało mi się, że całkiem dobrze rozumiem typy generyczne Javy, ale potem w java.lang.Enum natknąłem się na następujące rzeczy:
class Enum<E extends Enum<E>>
Czy ktoś mógłby wyjaśnić, jak interpretować ten parametr typu? Dodatkowe punkty za udostępnienie innych przykładów, w których można zastosować podobny parametr typu.
Odpowiedzi:
Oznacza to, że argument typu dla wyliczenia musi pochodzić z wyliczenia, które samo ma ten sam argument typu. Jak to się stało? Poprzez uczynienie argumentu type nowym typem. Więc jeśli mam wyliczenie o nazwie StatusCode, byłoby to równoważne z:
Teraz, jeśli sprawdzisz ograniczenia, mamy
Enum<StatusCode>
- więcE=StatusCode
. Sprawdźmy:E
rozszerza sięEnum<StatusCode>
? Tak! W porządku.Możesz zadawać sobie pytanie, o co w tym wszystkim chodzi :) Cóż, oznacza to, że API dla Enum może odnosić się do siebie - na przykład, będąc w stanie powiedzieć, że
Enum<E>
implementujeComparable<E>
. Klasa bazowa jest w stanie dokonywać porównań (w przypadku wyliczeń), ale może upewnić się, że porównuje ze sobą tylko właściwy rodzaj wyliczeń. (EDYCJA: Cóż, prawie - zobacz edycję na dole.)Użyłem czegoś podobnego w moim porcie C # ProtocolBuffers. Istnieją „wiadomości” (niezmienne) i „konstruktorzy” (zmienne, używane do tworzenia wiadomości) - i występują jako pary typów. Stosowane interfejsy to:
Oznacza to, że z wiadomości możesz uzyskać odpowiedni konstruktor (np. Aby skopiować wiadomość i zmienić kilka bitów), a od konstruktora możesz otrzymać odpowiednią wiadomość, gdy skończysz ją budować. To dobra robota, ale użytkownicy API nie muszą się tym przejmować - jest to horrendalnie skomplikowane i wymagało kilku iteracji, aby dostać się tam, gdzie jest.
EDYCJA: Zauważ, że nie powstrzymuje cię to przed tworzeniem dziwnych typów, które używają argumentu typu, który sam w sobie jest w porządku, ale nie jest tego samego typu. Celem jest raczej zapewnienie korzyści we właściwym przypadku niż ochrona przed złem przypadkiem.
Więc jeśli i
Enum
tak nie były obsługiwane „specjalnie” w Javie, można (jak zauważono w komentarzach) utworzyć następujące typy:Second
wdrożyłbyComparable<First>
raczej niżComparable<Second>
... aleFirst
samo byłoby w porządku.źródło
Enum
nie ma żadnych metod wystąpienia, które zwracają typ parametru typu.class Enum<E>
jest to wystarczające we wszystkich przypadkach. W Generics powinieneś używać bardziej restrykcyjnego ograniczenia tylko wtedy, gdy jest to rzeczywiście konieczne do zapewnienia bezpieczeństwa typów.Enum
podklasy nie zawsze wygenerowany automatycznie, tylko dlatego, że trzebaclass Enum<E extends Enum<?>>
przezclass Enum<E>
to możliwość dostępuordinal
docompareTo()
. Jednak jeśli się nad tym zastanowić, z językowego punktu widzenia nie ma sensu pozwalać na porównywanie dwóch różnych typów wyliczeń za pomocą ich liczb porządkowych. Dlatego implementacjaEnum.compareTo()
tego użyciaordinal
ma sens tylko w kontekścieEnum
automatycznie generowanych podklas. Gdybyś mógł ręcznie tworzyć podklasyEnum
,compareTo
prawdopodobnie musiałoby to byćabstract
.Poniżej znajduje się zmodyfikowana wersja wyjaśnienia z książki Java Generics and Collections : We have an
Enum
specifiedktóry zostanie rozszerzony do klasy
gdzie
...
ma być jakoś sparametryzowaną klasą bazową dla wyliczeń. Zastanówmy się, co to ma być. Cóż, jednym z wymagańSeason
jest to, że powinien on implementowaćComparable<Season>
. Więc będziemy potrzebowaćCzego możesz użyć do
...
tego, aby to zadziałało? Biorąc pod uwagę, że ma to być parametryzacjaEnum
, jedyny wybór jestEnum<Season>
taki, aby mieć:Tak
Enum
jest sparametryzowany na typach takich jakSeason
. Wyciągnij zSeason
i otrzymujesz, że parametrEnum
jest dowolnego typu, który spełniaMaurice Naftalin (współautor, Java Generics and Collections)
źródło
Season
implementujeComparable<Season>
?compareTo
metody musi być zadeklarowany jakoEnum
podtyp, w przeciwnym razie kompilator (poprawnie) powie, że nie ma liczby porządkowej.Enum
, byłoby to możliweclass OneEnum extends Enum<AnotherEnum>{}
, nawet przyEnum
zadeklarowaniu sposobu w tej chwili. Nie miałoby sensu porównywanie jednego typu wyliczenia z innym, więcEnum
icompareTo
tak nie miałoby to sensu, jak zostało zadeklarowane. Granice nie pomagają w tym.public class Enum<E extends Enum<?>>
też by wystarczyło.Można to zilustrować prostym przykładem i techniką, której można użyć do zaimplementowania połączonych wywołań metod dla podklas. W poniższym przykładzie
setName
zwraca,Node
więc łańcuch nie zadziała dlaCity
:Moglibyśmy więc odwołać się do podklasy w ogólnej deklaracji, tak aby
City
teraz zwracał prawidłowy typ:źródło
return (CHILD) this;
rozważ dodanie metody getThis ():protected CHILD getThis() { return this; }
Zobacz: angelikalanger.com/GenericsFAQ/FAQSections/…Node<T>
tak nie jest), ignoruję je, aby zaoszczędzić czas.return (SELF) this;
jest wkompilowanareturn this;
, więc możesz ją po prostu pominąć.Nie tylko ty zastanawiasz się, co to znaczy; zobacz blog Chaotic Java .
„Jeśli klasa rozszerza tę klasę, powinna przekazać parametr E. Granice parametru E dotyczą klasy, która rozszerza tę klasę o ten sam parametr E”.
źródło
Ten post całkowicie wyjaśnił mi problem „rekurencyjnych typów ogólnych”. Chciałem tylko dodać kolejny przypadek, w którym ta konkretna struktura jest konieczna.
Załóżmy, że masz ogólne węzły na ogólnym wykresie:
Następnie możesz mieć wykresy wyspecjalizowanych typów:
źródło
class Foo extends Node<City>
którym Foo nie jest związane z City.class Node<T>
?class Node<T>
jest w pełni zgodny z twoim przykładem.Jeśli spojrzysz na
Enum
kod źródłowy, ma on następujące cechy:Po pierwsze, co to
E extends Enum<E>
znaczy? Oznacza to, że parametr typu jest czymś, co pochodzi od Enum i nie jest sparametryzowany przez typ surowy (jest sparametryzowany przez siebie).Jest to istotne, jeśli masz wyliczenie
który, jeśli dobrze wiem, jest tłumaczony na
Oznacza to, że MyEnum otrzymuje następujące metody:
A co ważniejsze,
To sprawia, że
getDeclaringClass()
rzut do właściwegoClass<T>
obiektu.O wiele jaśniejszym przykładem jest ten, na który odpowiedziałem na to pytanie, w którym nie można uniknąć tej konstrukcji, jeśli chcesz określić ogólne ograniczenie.
źródło
compareTo
ani nic niegetDeclaringClass
wymagaextends Enum<E>
ograniczenia.Według Wikipedii ten wzór nazywa się Ciekawie powtarzający się wzór szablonu . Zasadniczo, używając wzorca CRTP, możemy łatwo odwołać się do typu podklasy bez rzutowania typów, co oznacza, że używając wzorca, możemy imitować funkcję wirtualną.
źródło