Za više informacija pogledajte ovaj FAQ:
http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#SECTION1
Evo ukratko nekih važnih točaka iz ovog FAQ-a.
Primjer (generičkog tipa):
klasa Pair<X,Y> {
privatni X prvi;
privatni Y drugi; javni par(X a1, Y a2) {
prvi = a1;
drugi = a2;
}
public X getFirst() { return prvi; }
public Y getSecond() { return second; }
public void setFirst(X arg) { first = arg; }
public void setSecond(Y arg) { second = arg; }
}
razred Par ima dva parametra tipa X i Y . Zamjenjuju se argumentima tipa kada je generički tip Par se instancira. Na primjer, u deklaraciji Par<Niz, Datum> parametar tipa X zamjenjuje se argumentom tipa Niz i Y zamjenjuje se sa Datum .
Opseg identifikatora X i Y je cijela definicija klase. U ovom opsegu dva tipa parametara X i Y koriste se kao da su tipovi (uz određena ograničenja). U gornjem primjeru, parametri tipa koriste se kao argument i tip povrata metoda instance i tipova polja instance.
Parametri tipa mogu se deklarirati s granicama. Granice daju pristup metodama nepoznatog tipa za koje se zalaže parametar tipa. U našem primjeru ne pozivamo metode nepoznatih vrsta X i Y . Zbog toga su dva parametra tipa neograničena.
Primjer (konkretnog parametriziranog tipa):
public void printPair( Par<String, Long> par) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Par<String, Long> granica = novo Par<String, Long> ("maksimum", 1024L);
printPair(ograničenje);
Primjer (parametriziranog tipa zamjenskog znaka):
public void printPair( Par<?,?> par) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Par<?,?> granica = novo Par<String, Long> ("maksimum", 1024L);
printPair(ograničenje);
Sve instance generičkog tipa dijele isti prikaz tipa vremena izvođenja, naime prikaz sirovog tipa. Na primjer, instancije generičkog tipa Popis , kao što je Popis<Datum> , Popis<Niz> , Popis<Long> , itd. imaju različite statičke tipove u vrijeme prevođenja, ali isti dinamički tip Popis u vrijeme izvođenja.
Glumačka postava sastoji se od dva dijela:
- statička provjera tipa koju provodi kompilator u vrijeme kompajliranja i
- dinamička provjera tipa koju izvodi virtualni stroj tijekom izvođenja.
Statički dio razvrstava besmislene glumce koji ne mogu uspjeti, poput glumačke postave iz Niz da Datum ili od Popis<Niz> da Popis<Datum> .
Dinamički dio koristi informacije o vrsti vremena izvođenja i provodi provjeru tipa za vrijeme izvođenja. Podiže a ClassCastException ako dinamički tip objekta nije ciljni tip (ili podtip ciljnog tipa) cast. Primjeri odljeva s dinamičnim dijelom su odljev iz Objekt da Niz ili od Objekt da Popis<Niz> . To su tzv. downcast-ovi, od nadtipa prema dolje prema podtipu.
Primjeri konkretnih parametriziranih tipova su Popis<Niz> , Map<String,Date> , ali ne Popis<? produžava Broj> ili Map<String,?> .
Ponekad se očekuje da a Popis<Objekt> bio bi supertip a Popis<Niz> , jer Objekt je supertip od Niz . Ovo očekivanje proizlazi iz činjenice da takva vrsta odnosa postoji za nizove: Objekt[] je supertip od Niz[] , jer Objekt je supertip od Niz . (Ovaj tip odnosa je poznat kao kovarijanca .) Odnos super-podtipa tipova komponenti proteže se u odgovarajuće tipove polja. Ne postoji takav odnos tipa za instancije generičkih tipova. (Parametrizirani tipovi su ne kovarijanta.)
Nedostatak super-podtipskog odnosa među instancacijama istog generičkog tipa ima različite posljedice. Evo primjera.
Primjer:
void printAll(ArrayList<Object> c) {
za (Objekt o : c)
System.out.println(o);
} ArrayList<String> popis = novi ArrayList<String>();
...ispunite popis...
ispisSve(popis); // pogreška
A ArrayList<String> objekt se ne može proslijediti kao argument metodi koja traži a ArrayList<Object> jer su dva tipa instancije istog generičkog tipa, ali za drugačiji tip argumente, i iz tog razloga oni nisu međusobno kompatibilni.
Konkretni parametrizirani tipovi su konkretne instancije generičkog tipa. Oni su gotovo kao tipovi; postoji samo nekoliko ograničenja. NE mogu se koristiti u sljedeće svrhe:
- za stvaranje nizova
- u rukovanju iznimkama
- u klasnom literalu
- u an instanceof izraz
Nizovi koji sadrže elemente čiji je tip konkretan parametrizirani tip su nedopušteni.
Primjer (nedopuštenog tipa polja):
static void test() {
Par<Integer,Integer>[] intPairArr = novo Par<Cijeli broj,Cijeli broj>[10] ; // pogreška
dodajElemente(intPairArr);
Pair<Integer,Integer> pair = intPairArr[1];
Cijeli broj i = pair.getFirst();
pair.setSecond(i);
}
static void addElements( Objekt[] objArr) {
objArr[0] = novi Par<Integer,Integer>(0,0);
objArr[1] = novi Pair<String,String>("",""); // trebao bi uspjeti s ArrayStoreException
}
Kompajler zabranjuje stvaranje nizova čiji je tip komponente konkretan parametrizirani tip, kao što je Par<Cijeli broj,Cijeli broj> u našem primjeru. U prethodnom unosu raspravljali smo o tome zašto je razumno da kompilator kvalificira a Par<Integer,Integer>[] kao nezakonito. Ključni problem je taj što kompajler i runtime sustav moraju osigurati da niz bude a homogena niz elemenata iste vrste. Jedna od tipskih provjera, naime array-store-check koju izvodi virtualni stroj tijekom izvođenja, ne uspijeva otkriti uvredljivo umetanje vanzemaljskog elementa. U primjeru drugo umetanje u addElements metoda bi trebala biti neuspješna, jer su dodavali par nizova nizu integralnih vrijednosti, ali ne pada na očekivani način. O razlozima je bilo riječi u prethodnom unosu.
Ako ne možemo koristiti nizove koji sadrže elemente čiji je tip konkretan parametrizirani tip, što ćemo koristiti kao zaobilazno rješenje?
Razmotrimo 3 moguća rješenja:
- niz sirovih vrsta
- polje neograničenog zamjenskog znaka parametriziranog tipa
- zbirka umjesto niza
Dok su nizovi konkretnih parametriziranih tipova nezakoniti, zbirke konkretnih parametriziranih tipova su dopuštene.
Parametrizirani tip sa zamjenskim znakom je instancacija generičkog tipa gdje je barem jedan argument tipa zamjenski znak. Primjeri parametariziranih tipova sa zamjenskim znakovima su Kolekcija<?> , Popis<? produžava Broj> , Usporednik<? super String> i Par<String,?> . Parametrizirani tip sa zamjenskim znakovima označava obitelj tipova koja sadrži konkretne instancije generičkog tipa. Vrsta zamjenskog znaka koja se koristi određuje koji konkretni parametrizirani tipovi pripadaju obitelji. Na primjer, tip s parametrima zamjenskih znakova Kolekcija<?> označava obitelj svih instancijacija Kolekcija sučelje bez obzira na argument tipa. Parametrizirani tip zamjenskog znaka Popis<? produžava Broj> označava obitelj svih tipova popisa gdje je tip elementa podtip Broj . Parametrizirani tip zamjenskog znaka Usporednik<? super String> je obitelj svih instancijacija Usporednik sučelje za tipove argumenata tipa koji su supertipovi Niz.
Parametrizirani tip sa zamjenskim znakovima nije tip u uobičajenom smislu (različit od neparametrizirane klase/sučelja ili sirovog tipa).
Parametrizirani tipovi zamjenskih znakova mogu se koristiti za tipkanje (poput neparametriziranih klasa i sučelja):
- kao argument i povratne vrste metoda
- kao tip polja ili lokalne referentne varijable
- kao tip komponente arrayas tipa argument drugih parametriziranih tipova
- kao ciljni tip u castovima
Ne samo da tipovi mogu biti generički, već i metode mogu biti generičke. Statičke i nestatičke metode kao i konstruktori mogu imati parametre tipa. Sintaksa za deklaraciju parametara formalnog tipa slična je sintaksi za generičke tipove. Odjeljak parametra tipa omeđen je uglatim zagradama i pojavljuje se prije tipa povrata metode. Njegova sintaksa i značenje identični su popisu parametara tipa generičkog tipa.
Evo primjera generičkog lijeka max metoda koja izračunava najveću vrijednost u kolekciji elemenata nepoznatog tipa A .
Primjer (generičke metode):
klase Zbirke {
javna statičnost <A proširuje Comparable<A>> A max(Zbirka< A > xs) {
Iterator< A > xi = xs.iterator();
A w = xi.sljedeći();
dok (xi.hasNext()) {
A x = xi.sljedeci();
if (w.compareTo(x) < 0) w = x;
}
povratak w;
}
}
The max metoda ima jedan parametar tipa, named A . To je držač mjesta za tip elementa zbirke na kojem metoda radi. Parametar tipa ima ograničenje; to mora biti tip koji je podtip Usporedivo<A>
Primjer (istog generičkog tipa, ovaj put s granice):
javno sučelje Comparable<T> {
public int compareTo(T arg);
}
javna klasa TreeMap<Key proteže Comparable<Key> ,Podaci>{
privatna statička klasa Entry<K,V> {
K ključ;
V vrijednost;
Unos<K,V> lijevo = null;
Unos<K,V> desno = null;
Unos<K,V> roditelj;
}
privatni prijelazni Entry<Key,Data> root = null;
...
private Entry<Key,Data> getEntry(ključ ključa) {
Entry<Key,Data> p = korijen;
Ključ k = ključ;
dok (p != null) {
int cmp = k. usporedi s(p.ključ) ;
ako (cmp == 0)
povratak p;
inače ako (cmp < 0)
p = p.lijevo;
drugo
p = p. desno;
}
vratiti null;
}
public boolean containsKey(ključ ključa) {
return getEntry(key) != null;
}
...
}
U gornjem primjeru, parametar tipa Ključ ima obvezu Usporedivo<Key> . Specifikacija granice ima dva učinka:
- Omogućuje pristup metodama koje veza specificira . U primjeru vezani Usporedivo<Key> daje pristup usporediti metoda koju želimo pozvati u implementaciji našeg TreeMap razreda.
- Samo tipovi “unutar granica” mogu se koristiti za instanciranje generičkog tipa. U primjeru, parametrizirani tip kao što je TreeMap<Broj, Niz> bio bi odbijen, jer tip Broj nije podvrsta Usporediv<Broj> . Parametrizirani tip poput TreeMap<String,String> bi se prihvatilo, jer je tip String unutar granica, tj. podtip je Usporedivo<String> .
Sve klase, sučelja i enum tipovi mogu se koristiti kao vezani za parametar tipa, uključujući ugniježđene i unutarnje tipove. Niti primitivni tipovi niti tipovi polja ne mogu se koristiti kao vezani za parametar tipa.
Primjeri (ograničenja parametara tipa):
klasa X0 <T proteže int > { ... } // pogreška
klasa X1 <T proširuje Objekt[] > { ... } // pogreška
klasa X2 <T proširuje Broj > { ... }
klasa X3 <T produžuje Niz > { ... }
klasa X4 <T produžuje Pokretljiv > { ... }
klasa X5 <T produžuje Nit.Stanje > { ... }
klasa X6 <T produžuje Popis > { ... }
klasa X7 <T produžuje Popis<Niz> > { ... }
klasa X8 <T produžuje Popis<? produžava Broj> > { ... }
klasa X9 <T produžuje Usporedivo<? super broj> > { ... }
klasa X10<T proteže se Map.Entry<?,?> > { ... }
Uzorak koda pokazuje da primitivni tipovi kao što su int i vrste nizova kao što su Objekt[] nisu dopušteni kao vezani za parametar tipa.
Tipovi klasa, kao npr Broj ili Niz , i vrste sučelja, kao što je Pokretljiv , dopušteni su kao vezani za parametar tipa.
Tipovi enuma, kao npr Nit.Stanje također su dopušteni kao vezani za parametar tipa. Nit.Stanje je primjer ugniježđenog tipa koji se koristi kao veza parametra tipa. Dopušteni su i nestatični unutarnji tipovi.
Neobrađeni tipovi dopušteni su kao vezani za parametar tipa; Popis je primjer.
Parametrizirani tipovi dopušteni su kao vezani za parametar tipa, uključujući konkretne parametrizirane tipove kao što su Popis<Niz> , ograničeni zamjenski znakovi parametrizirani tipovi kao što su Popis<? produžava Broj> i Usporedivo<? super Long> , i neograničeni zamjenski znakovi parametrizirani tipovi kao što su Map.Entry<?,?> . Veza koja je parametarizirani tip sa zamjenskim znakom dopušta kao argument tipa sve tipove koji pripadaju obitelji tipova koju označava zamjenski znak. Ograničenje parametariziranog tipa sa zamjenskim znakom daje samo ograničeni pristup poljima i metodama; ograničenja ovise o vrsti zamjenskog znaka.
Primjer (zamjenski znakovi parametriziranog tipa kao vezanog parametra tipa):
klasa X< T proširuje List<? produžava Broj> > {
public void someMethod(T t) {
t.add(novo Dugo(0L)); // pogreška
Broj n = t.ukloni(0);
}
}
klasa Test {
public static void main(String[] args) {
X<ArrayList< dugo >> x1 = novi X<ArrayList<Long>>();
X<ArrayList< Niz >> x2 = novi X<ArrayList<String>>(); // pogreška
}
}
Referentne varijable tipa T (parametar tipa) tretiraju se kao referentne varijable zamjenskog tipa (vezani parametar tipa). U našem primjeru posljedica je da prevodilac odbija pozivanje metoda koje uzimaju argument “nepoznatog” tipa za koji stoji parametar tipa, kao što je Nabrojati.dodati , jer je granica parametarizirani tip zamjenskog znaka s gornjom granicom.
Ujedno i vezani Popis<? produžava Broj> određuje tipove koji se mogu koristiti kao argumenti tipa. Prevodilac prihvaća sve argumente tipa koji pripadaju obitelji tipa Popis<? produžava Broj> , odnosno sve podvrste Popis s argumentom tipa koji je podtip Broj .
Parametar tipa može se koristiti kao granica drugog parametra tipa.
Primjer (parametra tipa koji se koristi kao veza parametra tipa):
klasa Triple <T> {
privatni T fst, snd, trd;
javni < U produžava T , V produžava T , W produžava T > Triple(U arg1, V arg2, W arg3) {
fst = arg1;
snd = arg2;
trd = arg3;
}
}
U ovom primjeru parametar tipa T parametrizirane klase koristi se kao veza parametara tipa U , V i W parametrizirane metode instance te klase.
Daljnje prilike za korištenje parametara tipa kao granica drugih parametara tipa uključuju situacije u kojima je ugniježđeni tip definiran unutar generičkog tipa ili je lokalna klasa definirana unutar generičke metode. Čak je dopušteno koristiti parametar tipa kao vezu drugog parametra tipa u istom odjeljku parametra tipa.
Veza koja je klasa daje pristup svim svojim javnim članovima, to jest javnim poljima, metodama i ugniježđenim tipovima. Samo konstruktori nisu dostupni, jer nema jamstva da podklasa vezane ima iste konstruktore kao i veza.
Primjer (klase koja se koristi kao veza parametra tipa):
javna klasa Superklasa { // statički članovi
javna enum EnumType {THIS, THAT}
javni statički objekt staticField;
public static void staticMethod() { ... }// nestatički članovi
javna klasa InnerClass { ... }
javni objekt nonStaticField;
public void nonStaticMethod() { ... }// konstruktori
public SuperClass() { ... }// privatni članovi
privatni objekt privateField;...
}public final class SomeClass<T extends Superklasa > {
privatni T objekt;
public SomeClass(T t) { object = t; }public String toString() {
povratak
"statički ugniježđeni tip : "+T.EnumType.class+"\n"
+"statično polje : "+T.staticField+"\n"
+"statička metoda : "+T.staticMethod()+"\n"
+"nestatički ugniježđeni tip: "+T.InnerClass.class+"\n"
+"nestatično polje : "+object.nonStaticField+"\n"
+"nestatična metoda : "+object.nonStaticMethod()+"\n"
+"konstruktor : "+(novo T())+"\n" // pogreška
+"privatni član : "+object.privateField+"\n" // pogreška
;
}
}
Vezani Superklasa daje pristup svojim ugniježđenim tipovima, statičkim poljima i metodama i nestatičkim poljima i metodama. Samo konstruktor nije dostupan. To je zato što se konstruktori ne nasljeđuju. Svaka podklasa definira svoje vlastite konstruktore i ne mora podržavati konstruktore svoje superklase. Stoga nema jamstva da će podklasa od Superklasa imat će isti konstruktor kao i njegova superklasa.
Kontekst u kojem Enum<E proširuje Enum<E>> ” Čini se da je deklaracija Enum klasa u paketu java.lang :
javna apstraktna klasa Enum<E proširuje Enum<E>> {
...
}
Vrsta Enum je zajednička osnovna klasa svih tipova nabrajanja. U Javi tip enumeracije kao što je Boja se prevodi u klasu Boja koji se proteže Enum<Boja> . Svrha superklase Enum je pružiti funkcionalnost koja je zajednička svim tipovima nabrajanja.
Ovdje je skica klase Enum :
javna apstraktna klasa Enum< E proteže Enum<E>> implementira Comparable< E >, Serializable {
privatno konačno ime niza;
public final String name() { ... } privatni završni int ordinal;
public final int ordinal() { ... }zaštićen Enum(naziv niza, int redni) { ... }
javni niz toString() { ... }
public final boolean equals(Object other) { ... }
public final int hashCode() { ... }
zaštićeni konačni Object clone() izbacuje CloneNotSupportedException { ... }
public final int compareTo( E o) { ... }javna završna klasa< E > getDeclaringClass() { ... }
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { ... }
}
Iznenađujuća značajka u deklaraciji “ Enum<E proširuje Enum<E>> ” je činjenica da je novodefinirana klasa Enum i njegov novodefinirani parametar tipa E pojaviti u granicama tog istog tipa parametra. To znači da je Enum tip mora biti instanciran za jedan od svojih podtipova. Kako bismo razumjeli zašto ovo ima smisla, uzmite u obzir da je svaki tip enuma preveden u podtip Enum .
Ovdje je izmišljen tip enuma Boja :
nabrajanje Boja {CRVENA, PLAVA, ZELENA}
Kompajler ga prevodi u sljedeću klasu:
javni završni razred Boja proteže se Enum<Boja> {
public static final Color[] values() { return (Color[])$VALUES.clone(); }
public static Color valueOf(String name) { ... } private Boja(String s, int i) { super(s, i); }javna statična finalna boja CRVENA;
javna statična finalna boja PLAVA;
javna statična finalna boja ZELENA;privatna statička konačna boja $VALUES[];
statički {
RED = nova boja("RED", 0);
PLAVA = nova boja("PLAVA", 1);
ZELENO = nova boja("ZELENO", 2);
$VRIJEDNOSTI = (nova boja[] { CRVENA, PLAVA, ZELENA });
}
}
Nasljeđe ima za posljedicu da Boja tip nasljeđuje sve metode implementirane u Enum<Boja> . Među njima je i usporediti metoda. The Boja. usporediti metoda bi vjerojatno trebala uzeti a Boja kao argument. Kako bi se to dogodilo klas Enum je generički i Enum.compareTo metoda uzima Enum parametar tipa E kao argument. Kao rezultat toga, tip Boja izvedeno iz Enum<Boja> nasljeđuje a usporediti metoda koja uzima a Boja kao i argument, točno kako treba.
Ako raščlanimo deklaraciju “ Enum<E proširuje Enum<E>> ” možemo vidjeti da ovaj obrazac ima nekoliko aspekata.
Prvo, postoji činjenica da je vezan parametar tipa sam tip: “ Enum <E se proteže Enum <E>> “. Osigurava da samo podtipovi tipa Enum dopušteni su kao argumenti tipa. (Teoretski, tip Enum može se instancirati na sebi, kao u Enum<Enum> , ali to sigurno nije namjera i teško je zamisliti situaciju u kojoj bi takva instancija bila korisna.)
Drugo, postoji činjenica da je parametar vezan tip parametrizirani tip Enum <E> , koji koristi parametar tipa E kao argument tipa vezanog. Ova deklaracija osigurava da odnos nasljeđivanja između podtipa i instancije Enum je oblika X proširuje Enum<X> “. Podtip kao što je “ X proširuje Enum<Y> ” ne može se deklarirati zbog argumenta tipa Y ne bi bilo unutar granica; samo podvrste Enum<X> su unutar granica.
Treće, postoji činjenica da Enum je generički na prvom mjestu. To znači da neke od metoda klase Enum uzeti argument ili vratiti vrijednost nepoznatog tipa (ili na drugi način ovisiti o nepoznatom tipu). Kao što već znamo, ovaj nepoznati tip će kasnije biti podtip X od Enum<X> . Dakle, u parametriziranom tipu Enum<X> , ove metode uključuju podtip X , i oni se nasljeđuju u podtip X . The usporediti metoda je primjer takve metode; nasljeđuje se iz nadklase u svaku podklasu i u svakom slučaju ima potpis specifičan za podklasu.
Ukratko, deklaracija “ Enum<E proširuje Enum<E>> ” se može dešifrirati kao: Enum je generički tip koji se može instancirati samo za svoje podtipove, a ti podtipovi će naslijediti neke korisne metode, od kojih neke uzimaju argumente specifične za podtip (ili na drugi način ovise o podtipu).
Parametri tipa donje granice klasa
Parametri tipa mogu imati nekoliko granica, kao u klasa Box<T proširuje Number> {...} . Ali parametar tipa ne može imati donju granicu, to jest, konstrukcija kao što je klasa Box<T super Broj> {...} nije dopušteno. Zašto ne? Odgovor je: besmisleno je jer vam ništa ne bi kupilo da je dopušteno. Pogledajmo zašto su parametri tipa donje granice klasa zbunjujući istražujući što znači gornja granica parametra tipa.
Gornja granica parametra tipa ima tri učinka:
- Ograničena instancija. Gornja granica ograničava skup tipova koji se mogu koristiti za instanciranje generičkog tipa. Ako proglasimo a klasa Box<T proširuje Number> {...} tada bi prevodilac osigurao da samo podtipovi od Broj može se koristiti kao argument tipa. Odnosno, a Kutija<Broj> ili a Kutija<Long> je dopušteno, ali a Kutija<Objekt> ili Kutija<Niz> bio bi odbijen
Primjer (ograničene instancije zbog gornje granice parametra tipa):
klasa Box<T extends Number> {
privatna T vrijednost;
public Box(T t) { value = t; }
...
} klasa Test {
public static void main(String[] args) {
Box<Long> boxOfLong = novi Box<Long>(0L); // dobro
Box<String> boxOfString = novi Box<String>(""); // pogreška: String nije unutar granica
}
}
- Pristup nestatičnim članovima. Gornja granica daje pristup svim javnim nestatičkim metodama i poljima gornje granice. U provedbi našeg klasa Box<T proširuje Number> {...} možemo pozvati sve javne nestatičke metode definirane u klasi Broj , kao što je intValue() na primjer. Bez gornje granice prevodilac bi odbio svako takvo pozivanje. Primjer (pristupa nestatičkim članovima zbog gornje granice parametra tipa):
klasa Box<T extends Number> {
privatna T vrijednost;
public Box(T t) { value = t; }
public int increment() { return value.intValue()+1; } // <= bila bi pogreška bez vezanog broja
...
}
- Tip Brisanje. Krajnja lijeva gornja granica koristi se za brisanje tipa i zamjenjuje parametar tipa u bajt kodu. U našem klasa Box<T proširuje Number> {...} sve pojave T bila bi zamijenjena gornjom granicom Broj . Na primjer, ako klasa Kutija ima privatno polje tipa T i metoda void skup (T sadržaj) za postavljanje ovog privatnog polja, tada bi polje bilo vrste Broj nakon brisanja tipa i metoda bi se prevela u metodu skup praznina (sadržaj broja) .
Primjer (upotrebe gornje granice parametra tipa u brisanju tipa – prije brisanje tipa):
klasa Box< T proširuje broj > {
privatni T vrijednost;
javna kutija ( T t) { vrijednost = t; }
...
}
- Primjer (upotrebe gornje granice parametra tipa u brisanju tipa –
nakon
- brisanje tipa):
klasa Box {
privatni Broj vrijednost;
javna kutija ( Broj t) { vrijednost = t; }
...
}
- Osim toga, krajnja lijeva gornja granica pojavljuje se na daljnjim mjestima, kao što su automatski umetnuti odljevi i metode mosta.
Da su niže granice dopuštene za parametre tipa, koje bi nuspojave imale ili trebale imati? Ako konstrukt kao što je klasa Box<T super Broj> {...} bili dopušteni, što bi to značilo? Što bi 3 nuspojave gornje granice parametra tipa – ograničena instancija, pristup nestatičkom članu, brisanje tipa – značile za donju granicu?
Ograničene instance. Prevodilac može ograničiti skup tipova koji se mogu koristiti za instanciranje generičkog tipa s parametrom tipa donje granice. Na primjer, kompajler može dopustiti instanciranje kao što je Kutija<Broj> i Kutija<Objekt> od a Kutija<T super Broj> i odbaciti instancije kao što su Kutija<Long> ili Kutija<Kratka> . To bi bio učinak u skladu s restriktivnim nuspojavama opisanim za gornje granice parametara tipa.
Pristup nestatičnim članovima. Niža granica parametra tipa ne daje pristup nijednoj određenoj metodi osim onih naslijeđenih iz klase Objekt . U primjeru Kutija<T super Broj> nadtipovi od Broj nemaju ništa zajedničko, osim što su referentni tipovi i stoga podtipovi Objekt . Prevodilac ne može pretpostaviti da polje tipa T je tipa Broj ili njegov podtip. Umjesto toga, polje tipa T može biti bilo kojeg supertipa Broj , kao što je Serializable ili Objekt . Pozivanje metode kao što je intValue() više nije siguran za tip i kompajler bi ga morao odbaciti. Kao posljedica toga, donja granica parametra tipa ne bi dala pristup nestatičkim članovima izvan onih definiranih u klasi Objekt i stoga ima isti učinak kao “no bound”.
Tip Brisanje . Slijedeći ovu logiku, nema smisla zamijeniti pojavljivanja parametra tipa njegovom krajnjom lijevom donjom granicom. Deklariranje metode poput konstruktora kutija( T t) kao konstruktor kutija( Broj t) nema smisla, s obzirom na to T zamjenjuje se nadtipom od Broj . An Objekt može se ispravno proslijediti konstruktoru u instanci Kutija<Objekt> a konstruktor bi ga odbio. Ovo bi bilo potpuno pogrešno. Dakle, brisanje tipa bi zamijenilo sva pojavljivanja varijable tipa T prema vrsti Objekt , a ne svojom donjom granicom. Opet, donja granica bi imala isti učinak kao “bez granice”.
Želite li shvatiti što bi značilo da su oba niža _i_ gornje granice su bile dopuštene? Osobno, ne želim ni razmišljati o tome i radije bih to zaveo pod “neupravljivo”, ako dopuštate.
Suština je: sve to “ super “vezan bi vas kupio je ograničenje koje samo nadtipovi Broj mogu se koristiti kao argumenti tipa. A i to se često krivo shvaća. To NE bi značilo to razred Box<T super Number> {...} sadrži samo instance supertipova Broj . Upravo obrnuto – kao što pokazuje primjer u nastavku!
Primjer (upotrebe gornje granice parametra tipa u brisanju tipa – prije brisanje tipa):
klasa Box< T super broj > {
privatni T vrijednost;
javna kutija ( T t) { vrijednost = t; }
...
}
Primjer (upotrebe gornje granice parametra tipa u brisanju tipa – nakon brisanje tipa):
klasa Box {
privatni Objekt vrijednost;
javna kutija ( Objekt t) { vrijednost = t; }
...
}
A klasa Box<T super Broj> {...} bilo bi prevedeno brisanjem tipa u a Kutija koji sadrži Objekt polje i njegov konstruktor bili bi prevedeni na Kutija (objekt t) . To se bitno razlikuje od a klasa Box<T proteže se Broj> {...} , što bi u prijevodu bilo a Kutija koji sadrži a Broj polje i njegov konstruktor bili bi prevedeni na Kutija (broj t) . Posljedično, a Kutija<Broj> instancirano iz a klasa Box<T proširuje Number> {...} bilo bi drugačije od a Kutija<Broj> instancija iz a razred Box<T super Number> {...} , što će vjerojatno izazvati zabunu. Iz tog razloga donje granice nemaju smisla za parametre tipa klasa.
Parametri tipa donje granice metoda
U kombinaciji s metodama i njihovim tipovima argumenata, parametar tipa s donjom granicom povremeno može biti koristan.
Primjer (metode koja bi imala koristi od parametra tipa s donjom granicom):
klasa Pair<X,Y> {
privatni X prvi;
privatni Y drugi;
...
javni < Super X , B super Y > B addToMap(Map<A,B> map) { // pogreška: parametar tipa ne može imati donju granicu
return map.put(prvi, drugi);
}
}
klasa Test {
public static void main(String[] args) {
Par<String,Long> par = novi Par<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Broj broj = pair.addToMap(map);
}
}
The addToMap() metoda dodaje sadržaj para na kartu. Svaka karta koja može sadržavati supertipove X i Y bi učinio. Karta je staviti() metoda vraća vrijednost pronađenu u mapi za dati ključ, ako već postoji unos ključ-vrijednost za ključ u mapi. Povratna vrijednost karte staviti() metoda se vraća iz addToMap() metoda. Pod ovim okolnostima željeli bismo deklarirati metodu kao što je prikazano gore: Mapa je parametrizirana supertipovima parametara tipa para i addToMap() povratni tip metode je tip vrijednosti mape.
Since the compiler does not allow lower bounds on type parameters we need a work-around.
One work-around that comes to mind is the use of a wildcard, because wildcards can have a lower bound. Ovdje je zaobilazno korištenje zamjenskog znaka.
Example (of a work-around for the previous example using wildcards):
klasa Pair<X,Y> {
privatni X prvi;
privatni Y drugi;
...
javnost Objekt addToMap(Map< ? Super X , ? super Y > karta) {
return map.put(prvi, drugi);
}
}
klasa Test {
public static void main(String[] args) {
Par<String,Long> par = novi Par<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Broj broj = (Broj) pair.addToMap(mapa);
}
}
It works, except that there is no way to declare the return type as desired. Bio bi to supertip Y that the compiler captures from the map type, but there is no syntax for specifying it. Ne smijemo proglasiti vrstu povrata “ ? super Y “, jer” ? super Y ” je zamjenski znak, a ne vrsta i stoga nije dopušten kao vrsta povrata. Nemamo izbora i moramo koristiti Objekt umjesto kao tip povrata naše metode. This rather unspecific return type in turn forces callers of the addToMap() metoda u pretvaranje povratne vrijednosti Objekt na njegovu stvarnu vrstu. Ovo nije baš ono što smo imali na umu.
Drugo rješenje je korištenje statičkih metoda. Here is a work-around with a static instead of a non-static method.
Example (of a work-around for the previous example using a static method):
klasa Pair<X,Y> {
privatni X prvi;
privatni Y drugi;
...
javnost statički < A , B ,X produžava A ,Y produžava B > B addToMap(par<X,Y> par, karta< A , B > karta) {
return map.put(par.prvi,par.drugi);
}
}
klasa Test {
public static void main(String[] args) {
Par<String,Long> par = novi Par<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Broj broj = Par .addToMap( par ,karta);
}
}
Generički addToMap() metoda ima četiri parametra tipa: dva rezervirana mjesta X i Y za tip para i dva rezervirana mjesta A i B za vrstu karte. A i B su supertipovi X i Y , jer X i Y deklariraju se s A i B kao njihove gornje granice. (Imajte na umu da su parametri tipa generičke metode X i Y nemaju nikakve veze s Par klase X i Y parametri. Imena X i Y ponovno se koriste za generičku metodu kako bi ih se lako prepoznalo kao parametre tipa para.) Pomoću četiri parametra tipa možemo deklarirati preciznu vrstu povrata po želji: ista je vrsta kao i vrsta vrijednosti karte.
Primjer (nezakonite uporabe parametra tipa u statičkom kontekstu generičke klase):
klasa SomeClass <T> {
// statički inicijalizator, statičko polje, statička metoda
statički {
Neka klasa< T > test = novi SomeClass< T >(); // pogreška
}
privatni statički T globalInfo; // pogreška
javnost statički T getGlobalInfo() { // pogreška
vratiti globalInfo;
}
// nestatički inicijalizator, nestatičko polje, nestatička metoda
{
Neka klasa< T > test = novi SomeClass< T >();
}
privatni T localInfo;
javnost T getLocalInfo() {
return localInfo;
}
// statički ugniježđeni tipovi
javnost statički razreda Neuspjeh proširuje iznimku {
privatno finale T info; // pogreška
javni neuspjeh ( T t) { info = t; } // pogreška
javnost T getInfo() { return info; } // pogreška
}
privatni sučelje Može se kopirati {
T kopirati(); // pogreška
}
privatni nabrajanje Država {
VALJAN, NEVALJAN;
privatni T info; // pogreška
public void setInfo( T t) { info = t; } // pogreška
javnost T getInfo() { return info; } // pogreška
}
// nestatički ugniježđeni tipovi
javnost razreda Pristupnik {
javnost T getInfo() { return localInfo; }
}
}
Primjer ilustrira da se parametar tipa ne može koristiti u statičkom kontekstu generičke klase. Također pokazuje da se ugniježđena sučelja i enum tipovi smatraju članovima statičnog tipa klase. Samo unutarnje klase, to jest nestatičke ugniježđene klase, mogu koristiti parametar tipa priložene generičke klase.
Opseg parametra tipa sučelja cijela je definicija sučelja, osim svih polja ili ugniježđenih tipova. To je zato što su polja i ugniježđeni tipovi definirani u sučelju implicitno statični.
Primjer (nezakonite upotrebe parametra tipa u generičkom sučelju):
sučelje SomeInterface <T> {
// polje
Neka klasa< T > vrijednost = novi SomeClass< T >(); // pogreška
// ugniježđeni tip
razreda Pristupnik {
javnost T getInfo() { // pogreška
povratna vrijednost.getGlobalInfo();
}
}
// metode
T getValue();
}
Primjer pokazuje da su polja sučelja implicitno statična, tako da se parametar tipa ne može koristiti bilo gdje u deklaraciji polja generičkog sučelja. Slično, ugniježđena klasa smatra se statičkom ugniježđenom klasom, a ne unutarnjom klasom, i iz tog razloga upotreba parametra tipa bilo gdje u ugniježđenoj klasi je nezakonita.
Zaobilazno rješenje za ugniježđene statičke klase i sučelja:
U slučaju statičkih ugniježđenih klasa i sučelja to nije velika nevolja. Kao zaobilazno rješenje možemo generirati samu statičku klasu ili sučelje.
Primjer (zaobilazno rješenje – generirajte ugniježđeni statički tip):
klasa Wrapper <T> {
privatno finale T theObject;
javni omotač( T t) { theObject = t; }
javnost T getWrapper() { return theObject; } javni Nepromjenjivi <T> makeImmutable() {
vratiti novi nepromjenjivi <T> (objekt);
}
javni promjenjivi <T> makeMutable() {
vrati novi promjenjivi <T> (objekt);
}
privatni statički <A> A makeClone(A theObject) { ... }javna statička finalna klasa nepromjenljiva <A> { // sada je generička klasa
privatno finale A theObject;javno nepromjenjivo( A arg) {
theObject = makeClone(arg);
}
javnost A getWrappedItem() {
return makeClone(theObject);
}
}
javna statička klasa Promjenjiva <A> {
... slično ...
}
}
Ne postoji takvo rješenje za ugniježđene enum tipove jer oni ne mogu biti generički.
Zaobilazno rješenje za ugniježđene statičke klase:
Ako je ugniježđeni statički tip klasa, alternativno rješenje bilo bi pretvaranje statičke klase u nestatičnu unutarnju klasu.
Primjer (zaobilazno rješenje – koristite unutarnju klasu):
klasa Wrapper <T> {
privatno finale T theObject;
javni omotač( T t) { theObject = t; }
javnost T getWrapper() { return theObject; } public Mutable makeMutable() {
povratak ovaj. novi promjenjivi(theObject);
}
public nepromjenjiv makeImmutable() {
povratak ovaj. novi nepromjenjivi(theObject);
}
privatni statički <A> A makeClone(A theObject) { ... }javna završna klasa nepromjenjiva { // više nije statična klasa
privatno finale T theObject;javno nepromjenjivo( T arg) {
theObject = makeClone(arg);
}
javnost T getWrappedItem() {
return makeClone(theObject);
}
}
javna klasa Promjenjiv
... slično ...
}
}
Ovo zaobilazno rješenje dolazi s određenim opterećenjem jer sve unutarnje klase imaju skrivenu referencu na instancu vanjskog tipa, što u ovom primjeru niti trebaju niti iskorištavaju; skrivena referenca je samo prtljaga. Često se unutarnje klase kombiniraju sa sučeljima kako bi unutarnja klasa ostala privatnim implementacijskim detaljem klase koja je obuhvaća. I mi možemo učiniti isto ovdje.
Primjer (prethodno zaobilazno rješenje pročišćeno):
klasa Wrapper <T> {
privatno finale T theObject;
javni omotač( T t) { theObject = t; }
javnost T getWrapper() { return theObject; } javni promjenjivi <T> makeMutable() {
return this.new Mutable(theObject);
}
javni Nepromjenjivi <T> makeImmutable() {
return this.new Immutable(theObject);
}
privatni statički <A> A makeClone(A theObject) { ... }javno sučelje Nepromjenjiv<S> {
S getWrappedItem();
}
javno sučelje Promjenjiv<S> {
S getWrappedItem();
void setWrappedItem(S arg);
}
privatni final class ImmutableImplementation implementira nepromjenjiv<T> {privatni konačni T theObject;
public ImmutableImplementation(T arg) {
theObject = makeClone(arg);
}
public T getWrappedItem() {
return makeClone(theObject);
}
}
privatni klasa Promjenjiva Implementacija implementira Mutable<T> {
... slično ...
}
}
Kao što vidite, ugniježđena javna sučelja moraju biti generička, dok unutarnje privatne klase mogu koristiti parametar tipa klase koja ih okružuje.

Ostavite odgovor