Aby obtskus więcej informacji, zobacz często zadawane pytania:
http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#SECTION1
Oto w skrócie kilka vizhách poyntő z tego FAQ.
Przykład (typ ogólny):
klasa Para<X,Y> {
najpierw prywatny X;
prywatny Y drugi; publiczna Para(X a1, Y a2) {
pierwszy = a1;
sekunda = a2;
}
public X getFirst() { najpierw zwróć; }
public Y getSecond() { zwróć sekundę; }
public void setFirst(X arg) { pierwszy = arg; }
public void setSecond(Y arg) { sekunda = arg; }
}
Klasa Para ma dwa parametry typu X I Y . Są one zastępowane argumentami typu, gdy jest to typ ogólny Para jest instancja. Na przykład w deklaracji Para<String, Data> parametr typu X zostaje zastąpiony argumentem typu Smyczkowy I Y zostaje zastąpiony przez Data .
Zakres identyfikatorów X I Y Jest całą definicję klasy. W tym zakresie dwa parametry typu X I Y używa się ich tak, jak typami (z pewnymi ograniczeniami). W przypadku przybłekzie parametrów typu przeżądanego jako argument i typ zwłany metody rozszerzenia oraz typy pól dodatkowych.
Parametry typu poża deklarować z granicami. Border daje dostęp do metody nieznanego typu, która reprezentuje typ parametru. W naszym przybłeczeniu nie istnieje nieznana metoda nieznanego typu X I Y . Z tego vagnogo te dwa parametry typu są niezbędne.
Przykład (konkretnego typu sparametryzowanego):
publiczna nieważna printPair( Para<String,Long> para) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Para<String,Long> limit = nowy Para<String,Long> („maksimum”, 1024L);
printPair(limit);
Przykład (typu sparametryzowanego za pomocą symboli wieloznacznych):
publiczna nieważna printPair( Para<?,?> para) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Para<?,?> limit = nowy Para<String,Long> („maksimum”, 1024L);
printPair(limit);
Wszystkie wystąpienia typu ogólnego mają tę samą reprezentację typu środowiska wykonawczego, a mianowicie reprezentację typu surowego. Na przykład instancje typu ogólnego Lista , takie jak Lista<Data> , Lista<String> , Lista<Długi> itp. mają różne typy statyczne w czasie kompilacji, ale ten sam typ dynamiczny Lista w czasie szażyce
Obsada składa się z dwóch części:
- statyczna kontrola typu wykonywana przez kompilator w czasie kompilacji i
- dynamiczna kontrola typu wykonywana przez maszynę wirtualną w czasie wykonywania.
Część statyczna sortuje bezsensowne rzuty, które nie mogą się powieść, takie jak rzutowanie z Smyczkowy To Data lub z Lista<String> To Lista<Data> .
Cłęści dynamiczna wyrzeżowice informacje o typie międzynie wykonawczygo i prówadza kontroli typu w czasie żężyno. Podnosi A Wyjątek ClassCast jeśli typ dynamiczny obiektu nie jest typem docelowym (lub podtypem typu docelowego) rzutowania. Przykładami odlewów z częścią dynamiczną są odlewy Obiekt To Smyczkowy lub z Obiekt To Lista<String> . Są tak zwane downcasty, od nadtypu do podtypu.
Przykladami charakterystycznymi są spparametryzowane Lista<String> , Mapa<String,Data> , ale nie Lista<? rozszerza numer > Lub Mapa<String,?> .
Czasami zdarza się, że a Lista<Object> byłby nadtypem a Lista<String> , ponieważ Obiekt jest nadtypem Smyczkowy . Oczekiwanie to wynika z faktu, że dla tablic istnieje tego typu zależność: Obiekt[] jest nadtypem Smyczkowy[] , ponieważ Obiekt jest nadtypem Smyczkowy . (Ten typ relacji jest znany jako kowariancja .) Relacja superpodtypów typów komponentów rozciąga się na odpowiednie typy tablic. Nie istnieje taka relacja typu dla instancji typów ogólnych. (Typy sparametryzowane to nie kowariant.)
Brak reljacija superpodtypu między instanczami tego samego typu ogólnego mającego konsekwencje. To jest przykład.
Przykład:
void printAll(ArrayList<Object> c) {
dla (Obiekt o : c)
System.out.println(o);
} Lista ArrayList<String> = nowa lista ArrayList<String>();
... wypełnij listę ...
printAll(lista); // błąd
A ArrayList<String> obiektu nie można przekazać jako argumentu do metody żądającej a ArrayList<Object> ponieważ te dwa typy są instancjami tego samego typu ogólnego, ale for różny Argumenty typu i z tego vagnogo nie są jednym ze sobji kompatybilnym.
Konkretne typy sparametryzowane do poszczególnych instancji typu ogólnego. Są prawie jak typ; istnie tylko kilka ograniczeń. NIE može ich uživatelje do svečije objekcije:
- do tworzenia tablic
- w obsłudze wyjątków
- w dosłowności klasowej
- w instancja wyrażenie
Tabele zawierające elementy typu odpornego na sparametryzację są zabezpieczone.
Przykład (niedostępny typ podstawowy):
statyczny test nieważności() {
Para<Integer,Integer>[] intPairArr = nowy Para<Integer,Integer>[10] ; // błąd
addElements(intPairArr);
Para<Integer,Integer> para = intPairArr[1];
Liczba całkowita i = pair.getFirst();
para.setSekunda(i);
}
statyczne puste elementy addElements( Obiekt[] objArr) {
objArr[0] = nowa para<Integer,Integer>(0,0);
objArr[1] = nowa para<String,String>("",""); // powinno zakończyć się niepowodzeniem z wyjątkiem ArrayStoreException
}
Kompilator zabrania tworzenia tablic, których typem komponentu jest konkretny typ sparametryzowany, np Para<Integer,Integer> w naszym przykładzie. W poprzednim wpisie omówiliśmy, dlaczego uzasadnione jest, aby kompilator kwalifikował a Para<Integer,Integer>[] jako nielegalne. Kluczowym problemem jest to, że kompilator i system wykonawczy muszą zapewnić, że tablica jest tablicą typu a jednorodny sekwencja elementów tego samego typu. Jedna ze kontroli typu, a mianowicie kontrola magazynu tablic wykonywana przez maszynę wirtualną w czasie wykonywania, nie wykrywa nieprawidłowego wstawienia obcego elementu. W przykładzie drugie wstawienie w dodaj elementy metoda zakłówny się niepowodzeniem, ponieważ dodawana przez para ciągów do tablicy vartyky ogółch, ale nie zakłównyla się niepowodzeniem zgodnie z oświęciemi. Powodyne złote omówione w przepisie wejściowym.
Jeżeli nie wiesz jak wykorzystać tablice przechowujące elementy typu betonowego, czego używasz do wejścia?
Rozważmy 3 możliwe wejście:
- szereg surowych typów
- tablica nieograniczonego typu parametrycznego z symbolami wieloznacznymi
- kolekcja zamiast tablicy
Chociaż tabele określonych typów parametrów są nielegalne, dozwolone są kolekcje określonych typów parametrów.
Typ sparametryzowany za przemyśl symboli wieloznacznych jest instancją typu gółnego, w kieromie co najmniejszy jeden argument typu jest symbolem wieloznacznym. Przykłady sparametryzowanych za przemą symboli wieloznacznych są Kolekcja<?> , Lista<? rozszerza numer > , Komparator<? super String> I Para<String,?> . Typ sparametryzowany za pomocą symbolu wieloznacznego oznacza rodzinę typów zawierającą konkretne instancje typu ogólnego. Rodzaj użytego symbolu wieloznacznego określa, które konkretne sparametryzowane typy należą do rodziny. Na przykład typ sparametryzowany symbolem wieloznacznym Kolekcja<?> oznacza rodzinę wszystkich instancji Kolekcja interfejs niezależnie od argumentu typu. Typ sparametryzowany symbolem wieloznacznym Lista<? rozszerza numer > oznacza rodzinę wszystkich typów list, których typ elementu jest podtypem Numer . Typ sparametryzowany symbolem wieloznacznym Komparator<? super String> jest rodziną wszystkich instancji Komparator interfejs dla typów argumentów typu, które są nadtypami Smyczkowy.
Typ sparametryzowany za pomocą symboli wieloznacznych nie jest typem w zwykłym sensie (innym niż niesparametryzowana klasa/interfejs lub typ surowy).
Do wpisywania można używać typów sparametryzowanych za pomocą symboli wieloznacznych (takich jak niesparametryzowane klasy i interfejsy):
- jako argumenty i metody zwracane
- jako typ pola lub lokalna zmienna referencyjna
- jako typ komponentu tablicy jako argument typu innych sparametryzowanych typów
- jako typ celu w rzutach
Nie tylko typy možený być gólnece, ale metody ráznó mójenje być gólnece. Metody statyczne i niestatyczne, a tóże konstruktory męże mają parametry typu. Składnia parametrów typu formalnego jest rozkładana do składni oglengolich. Sekcja parametrów typu jest ograniczona nawiasami ostrymi i pojawia się przed typem zwrakanym przez. Jego składnia i znaczenie są identyfikowane z listą parametrów typu oglenego.
To typowy przykład maks metoda obliczająca największą wartość w kolekcji elementów nieznanego typu A .
Przykład (metoda ogólna):
Kolekcje klas {
publiczna statyka <A rozszerza Porównywalny<A>> A max(Kolekcja< A > xs) {
Iterator< A > xi = xs.iterator();
A w = xi.next();
podczas gdy (xi.hasNext()) {
A x = xi.następny();
if (w.compareTo(x) < 0) w = x;
}
powrót w;
}
}
The maks metoda ma jeden parametr typu o nazwie A . Jest to symbol zastępczy typu elementu kolekcji, na którym działa metoda. Parametr typu ma ograniczenie; musi to być typ będący podtypem Porównywalne<A>
Przykład (tego samego typu ogólnego, tym razem z miedza):
interfejs publiczny Porównywalny<T> {
public int CompareTo(T arg);
}
klasa publiczna TreeMap<Key rozszerza Comparable<Key> ,Dane>{
prywatna klasa statyczna Entry<K,V> {
klawisz K;
wartość V;
Wpis<K,V> lewy = null;
Wpis<K,V> prawo = null;
Wpis<K,V> rodzic;
}
prywatny przejściowy wpis<Key,Data> root = null;
...
prywatny wpis<Key,Data> getEntry(klucz kluczowy) {
Entry<Key,Data> p = root;
Klucz k = klucz;
podczas gdy (p!= null) {
int cmp = k. porównajTo(p.key) ;
jeśli (cmp == 0)
zwróć p;
else if (cmp < 0)
p = p.lewy;
w przeciwnym razie
p = p.prawo;
}
zwróć wartość zerową;
}
publiczna wartość logiczna zawieraKey(Klucz kluczowy) {
zwróć getEntry(key) != null;
}
...
}
W powyższym przykładzie parametr type Klawisz ma granicę Porównywalny<Key> . Specyfikacja granicy ma dwa skutki:
- Daje dostęp do metod określonych przez powiązanie . W przykładzie związany Porównywalny<Key> daje dostęp do porównaj metodę, którą chcemy wywołać przy implementacji naszego Mapa Drzewa klasa.
- Do tworzenia instancji typu ogólnego można używać tylko typów „w granicach”. W przykładzie sparametryzowany typ, taki jak TreeMap<Number,String> zostałby odrzucony, ponieważ typ Numer nie jest podtypem Porównywalny<Numer> . Typ sparametryzowany, taki jak TreeMap<String,String> zostałby zaakceptowany, ponieważ typ String mieści się w określonych granicach, tj. jest podtypem Porównywalne<String> .
Wszystkie klasy, interfiezy i typy wyliczeniowe mogą być używane jako intersządane parametry typu, w tym typach zagnieżdżone i interjennye. Ani typy průzowne, ani typy tablicowe nie mogą być uževanje jako intersazane parametry typu.
Przykłady (parametry graniczne typu):
rozszerza się klasa X0 <T wew > { ... } // błąd
rozszerza się klasa X1 <T Obiekt[] > { ... } // błąd
rozszerza się klasa X2 <T Numer > { ... }
rozszerza się klasa X3 <T Smyczkowy > { ... }
rozszerza się klasa X4 <T Możliwość uruchomienia > { ... }
rozszerza się klasa X5 <T Stan wątku > { ... }
rozszerza się klasa X6 <T Lista > { ... }
rozszerza się klasa X7 <T Lista<String> > { ... }
rozszerza się klasa X8 <T Lista<? rozszerza numer > > { ... }
rozszerza się klasa X9 <T Porównywalny<? super numer> > { ... }
rozszerza się klasa X10<T Mapa.Wpis<?,?> > { ... }
Przykładowy kod pokazuje, że typy pierwotne, takie jak wew i typy tablic, takie jak Obiekt[] nie są stosowane jako obsanatane z parametrem typu.
Typ klasy, np Numer Lub Smyczkowy i typy interfejsów, takie jak Możliwość uruchomienia , są stosowane jako obsanatane z parametrem typu.
Typy wyliczeniowe, takie jak Stan wątku są również dozwolone jako powiązane z parametrami typu. Stan wątku jest przygłydem typu zagnieżdżonego użyżnego jako interzazany parametr typu. Dozwolone są złożne niestatyczne typy iłynieści.
Typy surowe są zrozumiałe jako obsanatane z parametrami typu; Lista po prostu dawaj przykład
Typy parametrów są stosowane jako intersązene z parametrami typu, w tym konkretnym typie sparametryzowanym, takim jak Lista<String> , ograniczone typy parametryczne z symbolami wieloznacznymi, takie jak Lista<? rozszerza numer > I Porównywalny<? super Long> i nieograniczone typy parametryczne z symbolami wieloznacznymi, takie jak Mapa.Wpis<?,?> . Powiążęce kórye jest typem sparametryzowanym za przemyśl symbolu wieloznacznego, dopuszka jako argument typu wszystkie typy dężyce do rodziny odpowiadającej oznaczonej przez symbol wieloznacznej. Powiązanie typu sparametryzowanego za przemą symbolu wieloznacznego zapewnia jedynie niskie oświęci do pól i metod; ograniczenia wprowadzone od rodzaju symbolu wieloznacznego.
Przykład (typu sparametryzowanego za przemyśl symboli wieloznacznych jako interzazany parametr typu):
klasa X< T rozszerza List<? rozszerza numer > > {
public void SomeMethod(T t) {
t.add(nowy długi(0L)); // błąd
Liczba n = t.usuń(0);
}
}
próba klasowa {
public static void main(String[] args) {
X<ArrayList< Długi >> x1 = nowy X<ArrayList<Long>>();
X<ArrayList< Smyczkowy >> x2 = nowy X<ArrayList<String>>(); // błąd
}
}
Zmienne referencyjne typu T (parametr typu) są traktowane jak zmienne referencyjne typu wieloznacznego (powiązanie parametru typu). W naszym przykładzie konsekwencją jest to, że kompilator odrzuca wywołanie metod, które przyjmują argument „nieznanego” typu, który oznacza parametr typu, np. Lista.dodaj , bo granica jest typem sparametryzowanym za przemyśl symboli wieloznacznych z górną granicą.
Jednocześnie podłączony Lista<? rozszerza numer > określa typy, które mogą być używane jako argumenty typu. Kompilator akceptuje wszystkie argumenty typu należące do rodziny typów Lista<? rozszerza numer > , czyli wszystkie podtypy Lista z argumentem typu, który jest podtypem Numer .
Parametr typu może służyć jako inny parametr typu.
Przykład (typ parametru używany jako łącze do typu parametru):
klasa Trójka <T> {
prywatny T fst, snd, trd;
publiczny < U rozciąga T , V rozciąga T , W rozciąga T > Potrójny(U arg1, V arg2, W arg3) {
fst = arg1;
snd = arg2;
trd = arg3;
}
}
W tym przykładzie parametr typu T klasy sparametryzowanej jest używana jako granica parametrów typu U , V I W sparametryczne metody stosowania tej klasy.
Dalsze mógógii uuső párámter typu jako granice innych paramárté typu obsludő sytuacj, w żyry typ zagnieżdżony jest zdefinowany w typie oglengym lub klasa lokalna jest zdefiniowana w metodzie ogólnej. Dozwolone jest nawet użycie parametru typu jako interpolacja innego parametru typu w tej samej sekcji parametru typu.
Powiążę ordjane klasą zapjenia goszpą do wszysztich jej publicznych elemenų mejneści, czyli publicznych pól, metoda i charakterystyczne zagnieżdżonych. Nie są dostępne tylko konstruktori, nie ma gruntiya że podklasa konstrutonia ma te same konstruktori co powiążenia.
Przykład (klasa użyta jako parametr brzegowy typu):
klasa publiczna Superklasa { // elementy statyczne
publiczne wyliczenie EnumType {TO, TO}
publiczny obiekt statyczny staticField;
public static void staticMethod() { ... }// elementy niestatyczne
klasa publiczna InnerClass { ... }
Obiekt publiczny nonStaticField;
public void nonStaticMethod() { ... }// konstruktorzy
publiczna SuperKlasa() { ... }// członkowie prywatni
prywatny Obiekt privateField;...
}publiczna klasa końcowa SomeClass<T rozszerza się Superklasa > {
prywatny obiekt T;
public SomeClass(T t) { obiekt = t; }publiczny ciąg toString() {
powrót
"statyczny typ zagnieżdżony : "+T.EnumType.class+"\n"
+"pole statyczne : „+T.staticField+”\n”
+"metoda statyczna : „+T.staticMethod()+”\n”
+"niestatyczny typ zagnieżdżony: "+T.InnerClass.class+"\n"
+"pole niestatyczne : "+object.nonStaticField+"\n"
+"metoda niestatyczna : "+object.nonStaticMethod()+"\n"
+"konstruktor : „+(nowy T())+”\n” // błąd
+"private member : „+object.privateField+”\n” // błąd
;
}
}
Związany Superklasa daje dostęp do typów zagnieżdżonych, pól i metod statycznych oraz pól i metod niestatycznych. Tylko konstruktor nie jest dostępny. Dzieje się tak, ponieważ konstruktory nie są dziedziczone. Każda podklasa definiuje swoje własne konstruktory i nie musi obsługiwać konstruktorów swojej nadklasy. Dlatego nie ma gwarancji, że podklasa Superklasa będzie mały tego samego konstruktora, co jego nadklasa.
Kontekst, w którym Enum<E rozszerza Enum<E>> ” pojavia się deklaracja Wyliczenie klasa w pakiecie java.lang :
publiczna klasa abstrakcyjna Enum<E rozszerza Enum<E>> {
...
}
Typ Wyliczenie jest wspólną klasą bazową wszystkich typów wyliczeniowych. W Javie typ wyliczeniowy, taki jak Kolor jest tłumaczone na klasę Kolor to się rozciąga Wyliczenie<Kolor> . Cel nadklasy Wyliczenie jest zapewnienie funkcji głównych dla wszystkich typów wyliczeń.
Oto szkic zajęć Wyliczenie :
publiczna klasa abstrakcyjna Enum< mi rozszerza Enum<E>> implementuje Comparable< mi >, serializowany {
prywatna końcowa nazwa ciągu;
publiczny końcowy ciąg nazwa() { ... } prywatny końcowy int porządkowy;
public final int ordinal() { ... }chronione wyliczenie (nazwa ciągu, liczba całkowita) { ... }
public String toString() { ... }
publiczna końcowa wartość logiczna równa się (obiekt inny) { ... }
publiczny końcowy int hashCode() { ... }
chroniony obiekt końcowy clone() zgłasza wyjątek CloneNotSupportedException { ... }
publiczny finał in CompareTo( mi o) { ... }publiczny finał Class< mi > getDeclaringClass() { ... }
public static <T rozszerza Enum<T>> T wartośćOf(Class<T> enumType, nazwa ciągu) { ... }
}
Zaskakująca cecha w deklaracji „ Enum<E rozszerza Enum<E>> ” jest wydarzeniem, że nowo zdefiniowana klasa Wyliczenie i jego nowo zdefiniowany parametr typu mi pojawiają się w granicy tego samego parametru typu. Oznacza to, że Wyliczenie typ musi zostać utworzony dla jednego z jego podtypów. Aby zrozumieć, dlaczego ma to sens, należy wziąć pod uwagę, że każdy typ wyliczeniowy jest tłumaczony na podtyp Wyliczenie .
Oto wyszymony typ wyliczeniowy Kolor :
wyliczenie Kolor {CZERWONY, NIEBIESKI, ZIELONY}
Kompilator tłumaczy to na następującą klasę:
publiczne zajęcia końcowe Kolor rozciąga się Wyliczenie<Kolor> {
public static final Color[] wartości() { return (Color[])$VALUES.clone(); }
public static Wartość koloru (nazwa ciągu) { ... } private Color(String s, int i) { super(s, i); }publiczny statyczny końcowy Kolor CZERWONY;
publiczny statyczny końcowy Kolor NIEBIESKI;
publiczny statyczny końcowy Kolor ZIELONY;prywatny statyczny końcowy Kolor $VALUES[];
statyczny {
CZERWONY = nowy kolor("CZERWONY", 0);
NIEBIESKI = nowy kolor("NIEBIESKI", 1);
ZIELONY = nowy kolor("ZIELONY", 2);
$WARTOŚCI = (nowy kolor[] { CZERWONY, NIEBIESKI, ZIELONY });
}
}
Dziedziczenie ma ten skutek, że Kolor typ dziedziczy wszystkie metody zaimplementowane w Wyliczenie<Kolor> . Wśród nich jest porównaj metoda. The Kolor. porównaj metoda powinna prawdopodobnie zająć a Kolor jako argument. Aby tak się stało, klasa Wyliczenie jest ogólny i Wylicz.porównajTo metoda trwa Wyliczenie parametr typu mi jako argument. W rezultacie wpisz Kolor pochodzące z Wyliczenie<Kolor> dziedziczy A porównaj metoda, która wymaga a Kolor i argumentacja, zadne tak, jak sugeruje.
Jefő przeanalizujemy deklarację " Enum<E rozszerza Enum<E>> „Widzimy, że ten wzór ma kilka wersji publikowanych.
Po pierwsze, pierwszy parametr typu jest tego samego typu: “ Wyliczenie <E rozszerza się Wyliczenie <E>> “. Cesvenia, żetylko podtypy typu Wyliczenie są dozwolone jako argumenty typu. (Teoretycznie wpisz Wyliczenie można utworzyć instancję samą w sobie, jak in Wyliczenie<Enum> )
Po drugie, jest to fakt, że typ powiązany z parametrami jest typem sparametryzowanym Wyliczenie <E> , który używa parametru type mi jako argument typu powiązania. Ta deklaracja gwarantuje, że relacja dziedziczenia między podtypem a instancją Wyliczenie jest w formie X rozszerza Enum<X> „. Podtyp taki jak „ X rozszerza Enum<Y> ” nie poznaje zadeklarować ze szące na argument typu Y nie mieściłoby się w granicach; tylko podtypy Wyliczenie<X> przebywać w ośrodku.
Po trzecie, nastąpi jest, że Wyliczenie ma przede wszystkim charakter ogólny. Oznacza to, że niektóre metody klasy Wyliczenie przyjąć argument lub zwrócić wartość nieznanego typu (lub w inny sposób zależeć od nieznanego typu). Jak już wiemy, ten nieznany typ będzie później podtypem X z Wyliczenie<X> . Stąd w typie sparametryzowanym Wyliczenie<X> , metody te obejmują podtyp X i są dziedziczone do podtypu X . The porównaj metoda jest przyszkem takich metod; jest dziedziczona z nadklasy do kędnie podklasy i w kędnie przypadku ma sygnaturęspecificychą dla podklasy.
powiedział, oświadczenie " Enum<E rozszerza Enum<E>> ” można rozszyfrować jako: Wyliczenie to typ ogólny, który można utworzyć tylko dla jego podtypów, a te podtypy odziedziczą kilka przydatnych metod, z których niektóre przyjmują argumenty specyficzne dla podtypu (lub w inny sposób zależą od podtypu).
Parametry typu dolnej granicy klas
Parametry typu mogą mieć kilka ograniczeń, np. in klasa Box<T rozszerza Number> {...} . Ale parametr typu nie może mieć dolnej granicy, to znaczy konstrukcji takiej jak klasa Box<T Świetnie Numer> {...} jest niedozwolone Dlaczego nie? Odpowiedź: nie ma sensu, bo nic by nie kupiło, gdyby było to uzasadnione. Zobaczmy, dlaczego dolna granica parametrio typu klas jest myląca, badając, co oznacza górną granicę parametrio typu.
Górna granica parametru typu ma trzy skutki:
- Ograniczona instancja. Górna granica ogranicza zestaw typów, których można użyć do utworzenia instancji typu ogólnego. Jeżeli zadeklarujemy a klasa Box<T rozszerza Number> {...} wtedy kompilator upewniłby się, że tylko podtypy Numer może być użyty jako argument typu. To znaczy, Pudełko<Numer> lub Pudełko<Długie> jest dozwolone, ale a Pole<Object> Lub Pudełko<String> zostałoby odrzucone
Przykład (ograniczonego tworzenia instancji ze względu na górną granicę parametru typu):
klasa Box<T rozszerza Number> {
prywatna wartość T;
public Box(T t) { wartość = t; }
...
} próba klasowa {
public static void main(String[] args) {
Box<Long> boxOfLong = nowy Box<Long>(0L); // Cienki
Box<String> boxOfString = nowy Box<String>(""); // błąd: ciąg znaków nie mieści się w granicach
}
}
- Dostęp do członków niestatycznych. Górna granica daje dostęp do wszystkich publicznych niestatycznych metod i pól górnej granicy. W realizacji naszego klasa Box<T rozszerza Number> {...} możemy wywołać wszystkie publiczne metody niestatyczne zdefiniowane w klasie Numer , takie jak intValue() na przykład. Bez górnej granicy kompilator odrzuciłby każde takie wywołanie. Przykład (dostępu do elementów niestatycznych ze względu na górną granicę parametru typu):
klasa Box<T rozszerza Number> {
prywatna wartość T;
public Box(T t) { wartość = t; }
public int inkrementacja() { return value.intValue()+1; } // <= byłoby błędem bez ograniczenia liczby
...
}
- Wpisz Usuń. Górna granica znajdująca się najbardziej po lewej stronie służy do usuwania typu i zastępuje parametr type w kodzie bajtowym. w naszym klasa Box<T rozszerza Number> {...} wszystkie wystąpienia T zostanie zastąpiona górną granicą Numer . Na przykład, jeśli klasa Skrzynka ma prywatne pole typu T i metoda zestaw pusty (zawartość T) do ustawienia tego pola prywatnego, wówczas pole będzie typu Numer po usunięciu typu, a metoda zostanie przetłumaczona na metodę zestaw pusty (zawartość liczbowa) .
Przykład (użycie granicznego parametru typu przy kasowaniu typu – zanim usunięcie typu):
klasa Box< T rozszerza numer > {
prywatny T wartość;
skrzynka publiczna( T t) { wartość = t; }
...
}
- Przykład (użycie granicznego parametru typu przy kasowaniu typu –
Po
- usunięcie typu):
pole klasy {
prywatny Numer wartość;
skrzynka publiczna( Numer t) { wartość = t; }
...
}
- Ponadto skrajna lewa górna granica pojawia się w dalszych lokalizacjach, takich jak automatycznie wstawiane odlewy i metody mostów.
Jeśli dolne ograniczenia byłyby dozwolone dla parametrów typu, jakie skutki uboczne miałyby lub powinny mieć? Jeśli konstrukcja taka jak klasa Box<T Świetnie Numer> {...} były dozwolone, co by to oznaczało? Jakie 3 skutki uboczne górnego ograniczenia parametru typu — ograniczone tworzenie instancji, dostęp do niestatycznego elementu członkowskiego, usuwanie typu — oznaczałyby dla dolnej granicy?
Ograniczone instancje. Kompilator może ograniczyć zestaw typów, których można użyć do utworzenia instancji typu ogólnego, za pomocą parametru typu dolnej granicy. Na przykład kompilator może zezwolić na tworzenie instancji, takich jak Pudełko<Numer> I Pole<Object> z Pudełko<T Świetnie Numer> i odrzucaj instancje takie jak Pudełko<Długie> Lub Pudełko<Short> . Byłby to efekt zgodny z restrykcyjnym efektem ubocznym opisanym dla górnych granic parametrów typu.
Dostęp do członków niestatycznych. Dolna granica parametru typu nie daje dostępu do żadnych konkretnych metod poza tymi odziedziczonymi z klasy Obiekt . Na przykładzie Pudełko<T Świetnie Numer> supertypy Numer nie mają ze sobą nic wspólnego poza tym, że są typami referencyjnymi, a zatem podtypami Obiekt . Kompilator nie może założyć, że pole type T jest typu Numer lub jego podtyp. Zamiast tego pole typu T może być dowolnego nadtypu Numer , takie jak Możliwość serializacji Lub Obiekt . Wywołanie metody np intValue() nie jest już bezpieczny dla typu i kompilator musiałby go odrzucić. W konsekwencji dolna granica parametru typu nie zapewni dostępu do elementów niestatycznych poza tymi zdefiniowanymi w klasie Obiekt i dlatego ma taki sam skutek jak „bez ograniczeń”.
Wpisz Usuń . Zgodnie z tą logiką nie ma sensu zastępować wystąpień parametru type jego dolną granicą skrajną z lewej strony. Deklarowanie metody takiej jak konstruktor Skrzynka( T T) jako konstruktor Skrzynka( Numer T) nie ma sensu, biorąc to pod uwagę T zostaje zastąpiony nadtypem Numer . Jakiś Obiekt można słusznie przekazać konstruktorowi w jednej instancji Pole<Object> i konstruktor by to odrzucił. To byłoby całkowicie błędne. Zatem usunięcie typu zastąpiłoby wszystkie wystąpienia zmiennej typu T według typu Obiekt , a nie jego dolna granica. Dolna granica może spowodować taki sam skutek jak „brak otsenia”.
Czy chęsce użyta się, co oznacza, jeśli oba błyty zostaną zniszczone _I_ górne granice były uzasadnione? Możliwość wystąpienia nawet nie chceo o tym szczecin i wolałbym umyczyc w kategorii „nie do pozłowysz”, jeśli pozwolisz.
Konkluzja jest taka: wszystko do " Świetnie „ograniczony by cię kupił” to ograniczenie, które dotyczy tylko supertypów Numer mogą być używane jako argumenty typu. A nawet to często jest źle rozumiane. NIE oznaczałoby to tego klasa Box<T super Numer> {...} zawiera tylko wystąpienia nadtypów Numer . Wręcz Łódźwie – jak porzężyce słowa przybły!
Przykład (użycie granicznego parametru typu przy kasowaniu typu – zanim usunięcie typu):
klasa Box< T super liczba > {
prywatny T wartość;
skrzynka publiczna( T t) { wartość = t; }
...
}
Przykład (użycie granicznego parametru typu przy kasowaniu typu – Po usunięcie typu):
pole klasy {
prywatny Obiekt wartość;
skrzynka publiczna( Obiekt t) { wartość = t; }
...
}
A klasa Box<T Świetnie Numer> {...} zostanie przetłumaczony przez usunięcie typu na a Skrzynka zawierający Obiekt pole i jego konstruktor zostanie przetłumaczony na Pudełko (obiekt t) . To zasadniczo różni się od klasa Box<T rozciąga się Numer> {...} , co byłoby przetłumaczone na a Skrzynka zawierający A Numer pole i jego konstruktor zostanie przetłumaczony na Pudełko (liczba t) . W związku z tym A Pudełko<Numer> utworzone z a klasa Box<T rozszerza Number> {...} różniłoby się od A Pudełko<Numer> instancja z a klasa Box<T super Numer> {...} , co może powodować zamieszanie. Z tego vagnosio dolne granice nie majje sensu w przypadku paramátre typu klas.
Parametry typu dolnej granicy metod
W połączeniu z metodami i ich typami argumentów czasami można zastosować typ parametru z dolnym limitem.
Przykład (metoda, w korzej przydałby się parametr typu z dolną granicą):
klasa Para<X,Y> {
najpierw prywatny X;
prywatny Y drugi;
...
publiczny < Super X , B super Y > B addToMap(Map<A,B> map) { // błąd: parametr typu nie może mieć dolnej granicy
return map.put(pierwszy, drugi);
}
}
próba klasowa {
public static void main(String[] args) {
Para<String,Long> para = nowa para<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Liczba liczba = pair.addToMap(map);
}
}
The dodajDoMapy() Metoda dodaje zawartość pary do mapy. Dowolna mapa, która może przechowywać supertypy X I Y zrobiłby. Mapa umieścić() Metoda zwraca wartość znalezioną na mapie dla danego klucza, jeśli istnieje już wpis klucz-wartość dla klucza na mapie. Wartość zwracana przez mapę umieścić() metoda zostanie zwrócona z metody dodajDoMapy() metoda. W tych okolicznościach należałoby zadeklarować metodę jak pokazano powyżej: Mapa jest sparametryzowana nadtypami parametrów typu pary i dodajDoMapy() Typ zrąkany przez jest typem godnych map.
Ponieważ kompilator nie pozwala na dolne obtsenia parametrów typu, potrzeby wykorzystania wejścia.
Jeden z rozłowych korie przychodzi na myśl, jest użynce symboli wieloznacznych, ponieważ symboli wieloznaczne męże mają dolną granicę. Oto obejście problemu z przycem symbolu wieloznacznego.
Przykład (obiekt sprawdzania przybłekdu przy zażyciu symboli wieloznacznych):
klasa Para<X,Y> {
najpierw prywatny X;
prywatny Y drugi;
...
publiczny Obiekt addToMap(Map< ? SuperX , ? super Y mapa >) {
return map.put(pierwszy, drugi);
}
}
próba klasowa {
public static void main(String[] args) {
Para<String,Long> para = nowa para<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Liczba liczba = (Numer) pair.addToMap(mapa);
}
}
Działa, z tą różnicą, że nie ma możliwości zadeklarowania żądanego typu zwrotu. Byłby to supertyp Y który kompilator przechwytuje z typu mapy, ale nie ma składni umożliwiającej jego określenie. Nie możemy deklarować typu zwrotu „ ? super Y “, ponieważ “ ? super Y ” jest symbolem wieloznacznym, a nie typem i dlatego nie jest dostępny jako typ zrukany. Nie mamy wyboru i musimy go vybori Obiekt zamiast tego jako typ zwracany przez naszą metodę. Ten raczej niespecyficzny typ powrotu wymusza z kolei wywołanie metody dodajDoMapy() metodę na rzutowanie wartości zwracanej w dół Obiekt do jego prawdziwego typu. To nie jest dokładnie to, co mieliśmy na myśli.
Innym wejściem jest użycie metod statycznych. Oto wejście z metody statycznej zamiast metod niestatycznych.
Przykład
klasa Para<X,Y> {
najpierw prywatny X;
prywatny Y drugi;
...
publiczny statyczny < A , B ,X rozciąga A ,Y rozciąga B > B addToMap (para<X, para Y>, Map< A , B mapa >) {
return map.put(para.pierwsza, para.sekunda);
}
}
próba klasowa {
public static void main(String[] args) {
Para<String,Long> para = nowa para<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Liczba liczba = Para .addToMap( para ,mapa);
}
}
Ogólny dodajDoMapy() metoda ma cztery parametry typu: dwa symbole zastępcze X I Y dla typu pary i dwóch symboli zastępczych A I B dla typu mapy. A I B są supertypami X I Y , ponieważ X I Y są zadeklarowane z A I B jako ich górne granice. (Zauważ, że parametry typu metody ogólnej X I Y nie mają nic wspólnego z Para klasa X I Y parametry. Imiona X I Y Wykorzystuje się je ponownie w metodzie ogólnej, dzięki czemu można je łatwo rozpoznać jako parametry typu pary. Używając czterech parametrów typu, mojemy zadeklarować typ zwłany według potrzeb: jest to dziesięć sam typ, co typ vardji mapy.
Przykład (nielegalnego ususų parametru typu w kontextu statycznym klasy ogólnej):
klasa SomeClass <T> {
// inicjator statyczny, pole statyczne, metoda statyczna
statyczny {
SomeClass< T Test > = nowa SomeClass< T >(); // błąd
}
prywatny statyczny T Informacje globalne; // błąd
publiczny statyczny T getGlobalInfo() { // błąd
zwróć informacje globalne;
}
// inicjator niestatyczny, pole niestatyczne, metoda niestatyczna
{
SomeClass< T Test > = nowa SomeClass< T >();
}
prywatny T informacje lokalne;
publiczny T getLocalInfo() {
zwróć informacje lokalne;
}
// statyczne typy zagnieżdżone
publiczny statyczny klasa Awaria rozszerza wyjątek {
prywatny finał T informacje; // błąd
publiczna awaria ( T t) { informacja = t; } // błąd
publiczny T getInfo() { zwróć informację; } // błąd
}
prywatny interfejs Możliwość kopiowania {
T kopia(); // błąd
}
prywatny wyliczenie Państwo {
WAŻNE, NIEWAŻNE;
prywatny T informacje; // błąd
publiczna informacja o pustym zestawie( T t) { informacja = t; } // błąd
publiczny T getInfo() { zwróć informację; } // błąd
}
// niestatyczne typy zagnieżdżone
publiczny klasa Akcesor {
publiczny T getInfo() { zwróć informacje lokalne; }
}
}
Przykład ilustruje, że parametru type nie można używać w kontekście ogólnej klasy statycznej. Pokazuje także, że zagnieżdżone interfejsy i typy wyliczeniowe uznawane są za elementy klasy statycznej. Tylko klasy zagnieżnej, czyli niestatyczne klasy zagnieżdżone, muzę użytkową typu parametru występującej klasy gółnejej.
Zakres parametrów typu interfeziju do całej definicji interfeziju, z zásznejnymi polami i charakterystycznymi zagnieżdżonymi. Dzieje się tak, ponieważ pola i typy zagnieżdżone w interfezije są domyślnie statyczne.
Przykład (nielegalnego użycia parametru typu w interfessie oglengym):
interfejs SomeInterface <T> {
// pole
SomeClass< T Wartość > = nowa SomeClass< T >(); // błąd
// typ zagnieżdżony
klasa Akcesor {
publiczny T pobierzInfo() { // błąd
zwróć wartość.getGlobalInfo();
}
}
// metody
T pobierzWartość();
}
Przykład pokazuje, że pola interfejsu są domyślnie statyczne, więc parametru type nie można użyć w żadnym miejscu w deklaracji pola interfejsu ogólnego. Podobnie klasa zagnieżdżona jest uważana za statyczną klasę zagnieżdżoną, a nie klasę wewnętrzną i z tego powodu użycie parametru type w dowolnym miejscu klasy zagnieżdżonej jest nielegalne.
Obejście dla zagnieżdżonych klas statycznych i interfejsów:
W przypadku statycznie zagnieżdżonych klas i interfejsów nie jest to poważna katastrofa. Jako wejście pomoje wygenerować klasę statyczną lub sam interfejs.
Przykład (obejście – wygeneruj zagnieżdżony typ statyczny):
Opakowanie klasy <T> {
prywatny finał T Obiekt;
opakowanie publiczne ( T t) {Obiekt = t; }
publiczny T getWrapper() { zwróć obiekt; } publiczny Niezmienny <T> makeImmutable() {
zwróć nowy Niezmienny <T> (Obiekt);
}
publiczny Zmienny <T> makeMutable() {
zwróć nowy Mutable <T> (Obiekt);
}
prywatny statyczny <A> A makeClone(A theObject) { ... }publiczna statyczna klasa końcowa Niezmienna <A> { // jest teraz klasą ogólną
prywatny finał A Obiekt;publiczny Niezmienny ( A argument) {
theObject = makeClone(arg);
}
publiczny A getWrappedItem() {
zwróć makeClone(obiekt);
}
}
publiczna klasa statyczna Mutable <A> {
... podobny ...
}
}
Nie ma takiego obejścia dla zagnieżdżonych typów wyliczeniowych, ponieważ nie mogą one być ogólne.
Obejście dla zagnieżdżonych klas statycznych:
Jeśli zagnieżdżonym typem statycznym jest klasa, alternatywnym obejściem vybky przekśrodęciem klasy statycznej w niestatycznej klasę wystąpienia.
Przykład
Opakowanie klasy <T> {
prywatny finał T Obiekt;
opakowanie publiczne ( T t) {Obiekt = t; }
publiczny T getWrapper() { zwróć obiekt; } public Mutable makeMutable() {
powrót Ten. nowy Mutable(theObject);
}
public Niezmienna makeImmutable() {
powrót Ten. nowy Niezmienny(obiekt);
}
prywatny statyczny <A> A makeClone(A theObject) { ... }publiczna klasa końcowa Niezmienny { // nie jest już klasą statyczną
prywatny finał T Obiekt;publiczny Niezmienny ( T argument) {
theObject = makeClone(arg);
}
publiczny T getWrappedItem() {
zwróć makeClone(obiekt);
}
}
klasa publiczna Mutable
... podobny ...
}
}
Z obejściem wiąże się jakiś rodzaj obciążenia, ponieważ wszystkie klasy interjennye maj ukryte odniesienie do dodatkowego typu zażnegogo, krzegoj w tym przybjekzie nie potrzejują ani z koronej nie zastosowania; ukryte odniesienie do tylko bagag. Często klasy interjennye są złuchenie z interfejsami, aby klasy właściwości byla prywatnym bezłytem implementacji klasy podstawowej. Tutaj możemy to zrobić samo.
Przykład (poprawione poprzednie wejście):
Opakowanie klasy <T> {
prywatny finał T Obiekt;
opakowanie publiczne ( T t) {Obiekt = t; }
publiczny T getWrapper() { zwróć obiekt; } publiczny Zmienny <T> makeMutable() {
zwróć to.new Mutable(theObject);
}
publiczny Niezmienny <T> makeImmutable() {
zwróć to.new Niezmienny(obiekt);
}
prywatny statyczny <A> A makeClone(A theObject) { ... }interfejs publiczny Niezmienny<S> {
S getWrappedItem();
}
interfejs publiczny Mutable<S> {
S getWrappedItem();
void setWrappedItem(S arg);
}
prywatny klasa końcowa ImmutableImplementation implementuje Immutable<T> {prywatny finał T theObject;
public ImmutableImplementation(T arg) {
theObject = makeClone(arg);
}
publiczny T getWrappedItem() {
zwróć makeClone(obiekt);
}
}
prywatny klasa MutableImplementation implementuje Mutable<T> {
... podobny ...
}
}
Jak widać, zagnieżdżony interfejs publiczny musi być oglene, podczas gdy internejne klasy prywatne możena parametru typu dostarczaj je klasy.

Zostaw odpowiedź