További információért tekintse meg ezt a GYIK-ot:
http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#SECTION1
Íme, röviden néhány fontos pont ebből a GYIK-ből.
Példa (általános típusra):
class Pair<X,Y> {
private X first;
private Y second; public Pair(X a1, Y a2) {
first = a1;
second = a2;
}
public X getFirst() { return first; }
public Y getSecond() { return second; }
public void setFirst(X arg) { first = arg; }
public void setSecond(Y arg) { second = arg; }
}
Az osztály Pár két típusú paraméterrel rendelkezik X és Y . Ezeket típus argumentumokkal helyettesítik, ha az általános típust Pár példányosodik. Például a nyilatkozatban Pár<karakterlánc, dátum> a típus paraméter X típus argumentum váltja fel Húr és Y is replaced by Dátum .
Az azonosítók hatóköre X és Y van az osztály teljes definíciója. Ebben a körben a két típusú paraméter X és Y típusként használják (bizonyos megszorításokkal). A fenti példában a típusparaméterek a példánymetódusok argumentumaként és visszatérési típusaként, valamint a példánymezők típusaiként használatosak.
A típusparaméterek korlátokkal deklarálhatók. A korlátok hozzáférést biztosítanak a type paraméter által jelzett ismeretlen típusú metódusokhoz. Példánkban nem hívunk meg semmilyen ismeretlen típusú metódust X és Y . Emiatt a két típusparaméter korlátlan.
Példa (konkrét paraméterezett típusra):
public void printPair( Pár<String,Long> pár) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Pár<String,Long> limit = új Pár<String,Long> ("maximum", 1024L);
printPair(limit);
Példa (a helyettesítő karakterekkel paraméterezett típusra):
public void printPair( Pair<?,?> pár) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Pair<?,?> limit = új Pár<String,Long> ("maximum", 1024L);
printPair(limit);
Egy általános típus minden példánya ugyanazt a futásidejű típusábrázolást használja, nevezetesen a nyers típus reprezentációját. Például egy általános típus példányai Lista , mint pl List<Date> , List<String> , List<Long> stb. különböző statikus típusokkal rendelkeznek a fordítási időben, de ugyanaz a dinamikus típus Lista futásidőben.
A szereposztás két részből áll:
- a fordító által a fordítási időben végrehajtott statikus típusellenőrzés és
- a virtuális gép által futás közben végrehajtott dinamikus típusellenőrzés.
A statikus rész kiválogatja az értelmetlen szereposztásokat, amelyek nem sikerülhetnek, mint például a szereposztás Húr hogy Dátum vagy től List<String> hogy List<Date> .
A dinamikus rész a futásidejű típusinformációkat használja, és futás közben típusellenőrzést hajt végre. Felveti a ClassCastException ha az objektum dinamikus típusa nem a leadott céltípusa (vagy a céltípus altípusa). Példák a dinamikus résszel rendelkező dobásokra a cast from Objektum hogy Húr vagy től Objektum hogy List<String> . Ezek az úgynevezett downcastok, a szupertípustól az altípusig.
Példák a konkrét paraméterezett típusokra List<String> , Map<String,Date> , de nem List<? kiterjeszti Number> vagy Map<String,?> .
Néha várható, hogy a List<Object> szupertípusa lenne a List<String> , mert Objektum egy szupertípusa Húr . This expectation stems from the fact that such a type relationship exists for arrays: Objektum[] egy szupertípusa Húr[] , mert Objektum egy szupertípusa Húr . (Ez a fajta kapcsolat az úgynevezett kovariancia .) A komponenstípusok szuper-altípus-kapcsolata kiterjed a megfelelő tömbtípusokra. Nem létezik ilyen típusú kapcsolat az általános típusok példányosítása esetén. (A paraméterezett típusok nem kovariáns.)
Az azonos általános típusú példányok közötti szuper-altípus-kapcsolat hiánya különféle következményekkel jár. Íme egy példa.
Példa:
void printAll(ArrayList<Object> c) {
for (Object o : c)
System.out.println(o);
} ArrayList<String> lista = new ArrayList<String>();
... töltse ki a listát ...
printAll(lista); // hiba
A ArrayList<String> objektum nem adható át argumentumként olyan metódusnak, amely a ArrayList<Object> mert a két típus ugyanannak az általános típusnak a példányosítása, de for különböző típusú argumentumokat, és emiatt nem kompatibilisek egymással.
A konkrét paraméterezett típusok egy általános típus konkrét példányai. Szinte olyanok, mint a típusok; csak néhány korlátozás van. NEM használhatók a következő célokra:
- tömbök létrehozásához
- kivételkezelésben
- osztályszó szerint
- egy an példánya kifejezés
A konkrét paraméterezett típusú elemeket tartalmazó tömbök illegálisak.
Példa (illegális tömbtípusra):
static void test() {
Pár<Integer,Integer>[] intPairArr = új Pair<Integer,Integer>[10] ; // hiba
addElements(intPairArr);
Pair<Integer,Integer> pair = intPairArr[1];
Integer i = pair.getFirst();
pair.setSecond(i);
}
static void addElements( Objektum[] objArr) {
objArr[0] = new Pair<Integer,Integer>(0,0);
objArr[1] = new Pair<String,String>("",""); // should fail with ArrayStoreException
}
A fordító tiltja olyan tömbök létrehozását, amelyek komponenstípusa konkrét paraméterezett típusú, mint pl Pair<Integer,Integer> példánkban. Az előző bejegyzésben tárgyaltuk, miért ésszerű, hogy a fordító minősítse a Pár<Integer,Integer>[] mint illegális. A fő probléma az, hogy a fordítónak és a futásidejű rendszernek biztosítania kell, hogy egy tömb a homogén azonos típusú elemek sorozata. Az egyik típusellenőrzés, nevezetesen a virtuális gép által futás közben végrehajtott tömbtár-ellenőrzés, nem észleli egy idegen elem sértő beillesztését. A példában a második beillesztés a addElements metódus meghiúsulhat, mert egy sztringpárt adtak hozzá az integrálértékek tömbjéhez, de ez nem sikerül a várt módon. Az okokat az előző bejegyzésben tárgyaltuk.
Ha nem használhatunk olyan tömböket tartalmazó elemeket, amelyek típusa egy konkrét paraméterezett típus, akkor mit használjunk megkerülő megoldásként?
Nézzünk meg 3 lehetséges megoldást:
- nyers típusok tömbje
- korlátlan helyettesítő karakterekkel paraméterezett típusú tömb
- tömb helyett gyűjtemény
Míg a konkrét paraméterezett típusok tömbjei illegálisak, a konkrét paraméterezett típusok gyűjteménye megengedett.
A helyettesítő karakterekkel paraméterezett típus egy általános típus példánya, amelyben legalább egy típus argumentum helyettesítő karakter. Példák a helyettesítő karakterekkel paraméterezett típusokra Gyűjtemény<?> , List<? kiterjeszti Number> , Összehasonlító<? szuper String> és Pár<String,?> . A helyettesítő karakterrel paraméterezett típus típuscsaládot jelöl, amely egy általános típus konkrét példányait tartalmazza. A használt helyettesítő karakter típusa határozza meg, hogy mely konkrét paraméterezett típusok tartoznak a családba. Például a helyettesítő karakterekkel paraméterezett típus Gyűjtemény<?> az összes példány családját jelöli Gyűjtemény interfész a típus argumentumtól függetlenül. A helyettesítő karakterekkel paraméterezett típus List<? kiterjeszti Number> denotes the family of all list types where the element type is a subtype of Szám . A helyettesítő karakterekkel paraméterezett típus Összehasonlító<? szuper String> az összes példány családja Összehasonlító interfész olyan típusú argumentumtípusokhoz, amelyek szupertípusai Húr.
A helyettesítő karakterekkel paraméterezett típus nem a szokásos értelemben vett típus (különbözik a nem paraméterezett osztálytól/interfésztől vagy a nyers típustól).
A helyettesítő karakterekkel paraméterezett típusok használhatók a gépeléshez (például nem paraméterezett osztályok és interfészek):
- mint argumentum és visszatérési típusú metódusok
- mező vagy helyi referenciaváltozó típusaként
- más paraméterezett típusok arrayas típusú argumentumának komponenstípusaként
- mint céltípus a dobásokban
Nemcsak a típusok lehetnek általánosak, hanem a módszerek is lehetnek általánosak. A statikus és nem statikus metódusoknak, valamint a konstruktoroknak lehetnek típusparaméterei. A formális típusparaméterek deklarálásának szintaxisa hasonló az általános típusok szintaxisához. A típusparaméter szakaszt szögletes zárójelek határolják, és a metódus visszatérési típusa előtt jelenik meg. Szintaxisa és jelentése megegyezik egy általános típus típusparaméter-listájával.
Íme egy általános példa max olyan módszer, amely a legnagyobb értéket számítja ki egy ismeretlen típusú elemek gyűjteményében A .
Példa (egy általános módszerre):
osztály Gyűjtemények {
public static Az <A kiterjeszti Összehasonlítható<A>> A max(Gyűjtemény< A > xs) {
Iterator< A > xi = xs.iterator();
A w = xi.next();
while (xi.hasNext()) {
A x = xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
A max A metódusnak egy típusparamétere van, a neve A . Ez a gyűjtemény azon elemtípusának helyőrzője, amelyen a metódus működik. A type paraméternek van korlátja; olyan típusnak kell lennie, amely altípusa Összehasonlítható<A>
Példa (ezúttal ugyanabból az általános típusból -vel határok):
nyilvános felület Összehasonlítható<T> {
public int compareTo(T arg);
}
nyilvános osztály TreeMap<Key kiterjeszti Comparable<Key> ,Data>{
private static class Entry<K,V> {
K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;
}
private transient Entry<Key,Data> root = null;
...
private Entry<Key,Data> getEntry(Key key) {
Entry<Key,Data> p = root;
Key k = key;
while (p != null) {
int cmp = k. összehasonlítás(p.kulcs) ;
if (cmp == 0)
return p;
else if (cmp < 0)
p = p.left;
else
p = p.right;
}
return null;
}
public boolean containsKey(Key key) {
return getEntry(key) != null;
}
...
}
A fenti példában a típus paraméter Kulcsfontosságú megvan a kötve Összehasonlítható<Key> . A kötés megadásának két hatása van:
- Hozzáférést biztosít a korlát által meghatározott metódusokhoz . A példában a kötött Összehasonlítható<Key> hozzáférést biztosít a összehasonlítani módszer, amelyet meg akarunk hívni a mi megvalósításunk során TreeMap osztály.
- Csak a “határokon belül” típusok használhatók az általános típus példányosítására. In the example, a parameterized type such as TreeMap<Number,String> elutasításra kerülne, mert a típus Szám nem altípusa Összehasonlítható<Szám> . Paraméterezett típus, mint pl TreeMap<String,String> elfogadnánk, mert a String típus a határokon belül van, azaz altípusa Összehasonlítható<String> .
Minden osztály, interfész és enum típus használható típusparaméterhez kötöttként, beleértve a beágyazott és a belső típusokat is. Sem primitív típusok, sem tömbtípusok nem használhatók típusparaméter-kötésként.
Példák (típusparaméter korlátokra):
osztály X0 <T kiterjeszti int > { ... } // hiba
osztály X1 <T kiterjeszti Objektum[] > { ... } // hiba
osztály X2 <T kiterjeszti Szám > { ... }
osztály X3 <T kiterjeszti Húr > { ... }
osztály X4 <T kiterjeszti Futható > { ... }
osztály X5 <T kiterjeszti Menet.Állam > { ... }
osztály X6 <T kiterjeszti Lista > { ... }
osztály X7 <T kiterjeszti List<String> > { ... }
osztály X8 <T kiterjeszti List<? kiterjeszti Number> > { ... }
osztály X9 <T kiterjeszti Összehasonlítható<? szuper szám> > { ... }
osztály X10<T kiterjeszti Térkép.Entry<?,?> > { ... }
A kódminta azt mutatja, hogy a primitív típusok, mint pl int és tömbtípusok, mint pl Objektum[] típusparaméterhez kötöttként nem engedélyezettek.
Az osztálytípusok, mint pl Szám vagy Húr , és interfésztípusok, mint pl Futható , típus paraméterhez kötött.
Enum típusok, mint pl Menet.Állam típusparaméterhez kötöttként is megengedettek. Menet.Állam egy példa egy beágyazott típusra, amelyet típusparaméter-kötésként használnak. Nem statikus belső típusok is megengedettek.
A nyers típusok típusparaméterhez kötöttekként megengedettek; Lista egy példa.
A paraméterezett típusok típusparaméterhez kötöttként engedélyezettek, beleértve a konkrét paraméterezett típusokat is, mint pl List<String> , korlátos helyettesítő karakterekkel paraméterezett típusok, mint pl List<? kiterjeszti Number> és Összehasonlítható<? szuper Long> , és a korlátlan helyettesítő karakterekkel paraméterezett típusok, mint pl Térkép.Entry<?,?> . A helyettesítő karakterrel paraméterezett kötés típus argumentumként minden olyan típust engedélyez, amely ahhoz a típuscsaládhoz tartozik, amelyet a helyettesítő karakter jelöl. A helyettesítő karakterekkel paraméterezett típuskorlát csak korlátozott hozzáférést biztosít a mezőkhöz és metódusokhoz; a korlátozások a helyettesítő karakter típusától függenek.
Példa (a helyettesítő karakterrel paraméterezett típus típusparaméterhez kötött):
X< osztály T kiterjeszti a List<-t? kiterjeszti Number> > {
public void someMethod(T t) {
t.add(new Long(0L)); // hiba
Number n = t.remove(0);
}
}
osztály teszt {
public static void main(String[] args) {
X<ArrayList< Hosszú >> x1 = new X<ArrayList<Long>>();
X<ArrayList< Húr >> x2 = új X<ArrayList<String>>(); // hiba
}
}
Típusú referenciaváltozók T (a típusparaméter) a helyettesítő karakter típusú referenciaváltozókként kezelendők (a típusparaméter kötött). Példánkban ennek az a következménye, hogy a fordító elutasítja olyan metódusok meghívását, amelyek a type paraméter által jelzett “ismeretlen” típusú argumentumot veszik fel, mint pl. List.add , mert a korlát helyettesítő karakterekkel paraméterezett típus felső korláttal.
Ugyanakkor a kötött List<? kiterjeszti Number> meghatározza a típus argumentumként használható típusokat. A fordító minden típusú argumentumot elfogad, amely a típuscsaládhoz tartozik List<? kiterjeszti Number> , azaz minden altípusa Lista típus argumentummal, amely altípusa Szám .
Egy típusparaméter egy másik típusparaméter korlátjaként használható.
Példa (típusparaméter kötésként használt típusparaméterre):
osztály Tripla <T> {
private T fst, snd, trd;
public < U kiterjeszti T , V kiterjeszti T , W kiterjeszti T > Triple(U arg1, V arg2, W arg3) {
fst = arg1;
snd = arg2;
trd = arg3;
}
}
Ebben a példában a típus paraméter T a paraméterezett osztályt használjuk a típusparaméterek korlátjaként U , V és W az adott osztály paraméterezett példánymetódusának.
További lehetőségek a típusparaméterek más típusparaméterek határaként történő használatára olyan helyzetekben, amikor egy beágyazott típust egy általános típuson belül, vagy egy helyi osztályt egy általános metóduson belül határoznak meg. Még az is megengedett, hogy egy típusparamétert egy másik típusparaméterhez kötve ugyanabban a típusparaméter részben.
Egy osztálynak minősülő kötés hozzáférést biztosít minden nyilvános tagjához, azaz nyilvános mezőihez, metódusaihoz és beágyazott típusaihoz. Csak a konstruktorok nem érhetők el, mert nincs garancia arra, hogy a kötés egy alosztálya ugyanazokkal a konstruktorokkal rendelkezik, mint a kötés.
Példa (egy típusparaméter kötéseként használt osztályra):
nyilvános osztály Szuperosztály { // static members
public enum EnumType {THIS, THAT}
public static Object staticField;
public static void staticMethod() { ... }// non-static members
public class InnerClass { ... }
public Object nonStaticField;
public void nonStaticMethod() { ... }// constructors
public SuperClass() { ... }// private members
private Object privateField;...
}nyilvános végső osztály SomeClass<T kiterjeszti Szuperosztály > {
private T object;
public SomeClass(T t) { object = t; }public String toString() {
return
"static nested type : "+T.EnumType.class+"\n"
+"static field : "+T.staticField+"\n"
+"static method : "+T.staticMethod()+"\n"
+"non-static nested type: "+T.InnerClass.class+"\n"
+"non-static field : "+object.nonStaticField+"\n"
+"non-static method : "+object.nonStaticMethod()+"\n"
+"constructor : "+(new T())+"\n" // error
+"private member : "+object.privateField+"\n" // error
;
}
}
A kötött Szuperosztály hozzáférést biztosít beágyazott típusaihoz, statikus mezőihez és metódusaihoz, valamint nem statikus mezőihez és metódusaihoz. Csak a kivitelező nem elérhető. Ez azért van, mert a konstruktorok nem öröklődnek. Minden alosztály meghatározza a saját konstruktorait, és nem kell támogatnia a szuperosztály konstruktorait. Ezért nincs garancia arra, hogy egy alosztály Szuperosztály ugyanolyan konstruktora lesz, mint a szuperosztályának.
A kontextus, amelyben Az Enum<E kiterjeszti az Enum<E>>-t ” jelenik meg a nyilatkozata Enum osztály csomagban java.lang :
nyilvános absztrakt osztály Az Enum<E kiterjeszti az Enum<E>>-t {
...
}
A típus Enum az összes felsorolástípus közös alaposztálya. Java nyelven egy felsorolástípus, mint pl Szín osztályra fordítják Szín amely kiterjed Enum<Color> . A szuperosztály célja Enum célja, hogy olyan funkcionalitást biztosítson, amely minden felsorolási típusra jellemző.
Itt van egy vázlat az osztályról Enum :
nyilvános absztrakt osztály Enum< E kiterjeszti Enum<E>> Comparable< valósítja meg E >, sorozható {
private final String name;
public final String name() { ... } private final int ordinal;
public final int ordinal() { ... }protected Enum(String name, int ordinal) { ... }
public String toString() { ... }
public final boolean equals(Object other) { ... }
public final int hashCode() { ... }
protected final Object clone() throws CloneNotSupportedException { ... }
public final int compareTo( E o) { ... }public final Class< E > getDeclaringClass() { ... }
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { ... }
}
A meglepő jellemző a nyilatkozatban “ Az Enum<E kiterjeszti az Enum<E>>-t ” az a tény, hogy az újonnan meghatározott osztály Enum és annak újonnan definiált típusparamétere E azonos típusú paraméter korlátjában jelennek meg. Ez azt jelenti, hogy a Enum típust példányosítani kell valamelyik altípusához. Annak megértéséhez, hogy ennek miért van értelme, vegyük figyelembe, hogy minden enumtípust lefordítanak az altípusba Enum .
Itt van a kitalált enum típus Szín :
enum Szín {PIROS, KÉK, ZÖLD}
A fordító a következő osztályba fordítja le:
nyilvános záróóra Szín kiterjed Enum<Color> {
public static final Color[] values() { return (Color[])$VALUES.clone(); }
public static Color valueOf(String name) { ... } private Color(String s, int i) { super(s, i); }public static final Color RED;
public static final Color BLUE;
public static final Color GREEN;private static final Color $VALUES[];
static {
RED = new Color("RED", 0);
BLUE = new Color("BLUE", 1);
GREEN = new Color("GREEN", 2);
$VALUES = (new Color[] { RED, BLUE, GREEN });
}
}
Az öröklődésnek az a hatása, hogy a Szín type örökli az összes olyan metódust, amely a -ben implementált Enum<Color> . Köztük van a összehasonlítani módszer. A Szín. összehasonlítani módszernek valószínűleg a Szín érvként. Annak érdekében, hogy ez megtörténjen, osztály Enum általános és a Enum.compareTo módszert vesz igénybe Enum típus paramétere E érvként. Ennek eredményeként írja be Szín származó Enum<Color> örököl a összehasonlítani módszer, amely a Szín ahogy és érvelés, pontosan úgy, ahogy kell.
Ha boncolgatjuk a nyilatkozatot” Az Enum<E kiterjeszti az Enum<E>>-t “Láthatjuk, hogy ennek a mintának több aspektusa van.
Először is az a tény, hogy a típusparaméter-kötés maga a típus: “ Enum Az <E meghosszabbodik Enum <E>> “. Biztosítja, hogy csak a típus altípusai Enum típus argumentumként engedélyezettek. (Elméletileg írja be Enum önmagában is példányosítható, mint pl Enum<Enum> , de ez természetesen nem szándékos, és nehéz elképzelni olyan helyzetet, amelyben egy ilyen példányosítás hasznos lenne.)
Másodszor, ott van az a tény, hogy a paraméterhez kötött típus a paraméterezett típus Enum <E> , amely a type paramétert használja E mint a kötött típusargumentuma. Ez a deklaráció biztosítja, hogy az öröklődési kapcsolat egy altípus és egy példánya között fennáll Enum alakú Az X kiterjeszti az Enum<X>-t “. Egy altípus, például “ Az X kiterjeszti az Enum<Y>-t ” nem deklarálható a type argumentum miatt Y nem lenne határokon belül; csak altípusai Enum<X> határokon belül vannak.
Harmadszor, ott van az a tény, hogy Enum elsősorban általános. Ez azt jelenti, hogy néhány módszer az osztály Enum vegyen egy argumentumot, vagy adjon vissza egy ismeretlen típusú értéket (vagy más módon függjön egy ismeretlen típustól). Mint már tudjuk, ez az ismeretlen típus később egy altípus lesz X -ból Enum<X> . Ezért a paraméterezett típusban Enum<X> , ezek a módszerek az altípust érintik X , és az altípusba öröklődnek X . A összehasonlítani a módszer egy példa egy ilyen módszerre; a szuperosztályból öröklődik minden alosztályba, és minden esetben alosztályspecifikus aláírással rendelkezik.
Összefoglalva, a nyilatkozat “ Az Enum<E kiterjeszti az Enum<E>>-t ” a következőképpen fejthető meg: Enum egy általános típus, amely csak az altípusaihoz példányosítható, és ezek az altípusok örökölnek néhány hasznos metódust, amelyek közül néhány altípus-specifikus argumentumokat vesz fel (vagy más módon függ az altípustól).
Osztályok alsó korlátos típusparaméterei
A típusparamétereknek több határa is lehet, például in osztály Box<T kiterjeszti Number> {...} . De egy típusparaméternek nem lehet alsó korlátja, vagyis olyan konstrukció, mint pl osztályú Box<T nagy Number> {...} nem megengedett. Miért ne? A válasz: értelmetlen, mert nem venne semmit, ha megengedné. Nézzük meg, miért zavaróak az osztályok alsó határának típusparaméterei, ha megvizsgáljuk, mit jelent egy típusparaméter felső korlátja.
A típusparaméter felső határának három hatása van:
- Korlátozott példányosítás. A felső korlát korlátozza az általános típus példányosításához használható típuskészletet. Ha kijelentjük a osztály Box<T kiterjeszti Number> {...} akkor a fordító gondoskodna arról, hogy csak az altípusai Szám típus argumentumként használható. Vagyis a Doboz<Szám> vagy a Doboz<Long> megengedett, de a Box<Object> vagy Box<String> elutasításra kerülne
Példa (korlátozott példányosításra egy típusparaméter felső korlátja miatt):
class Box<T extends Number> {
private T value;
public Box(T t) { value = t; }
...
} osztály teszt {
public static void main(String[] args) {
Box<Long> boxOfLong = new Box<Long>(0L); // rendben
Box<String> boxOfString = new Box<String>(""); // hiba: A karakterlánc nincs a határokon belül
}
}
- Hozzáférés a nem statikus tagokhoz. A felső korlát hozzáférést biztosít az összes nyilvános, nem statikus metódushoz és a felső korlát mezőjéhez. A mi osztály Box<T kiterjeszti Number> {...} az osztályban meghatározott összes nyilvános nem statikus metódust meg tudjuk hívni Szám , mint pl intValue() például. A felső korlát nélkül a fordító minden ilyen hívást elutasítana. Példa (nem statikus tagokhoz való hozzáférésre egy típusparaméter felső korlátja miatt):
class Box<T extends Number> {
private T value;
public Box(T t) { value = t; }
public int increment() { return value.intValue()+1; } // <= hiba lenne a számhoz kötött nélkül
...
}
- Írja be: Törlés. A bal szélső felső korlát a típustörlésre szolgál, és helyettesíti a típusparamétert a bájtkódban. A miénkben osztály Box<T kiterjeszti Number> {...} minden előfordulása T a felső korlát váltaná fel Szám . Például, ha osztály Doboz privát típusú mezővel rendelkezik T és egy módszer üres halmaz (T tartalom) ennek a privát mezőnek a beállításához, akkor a mező típusú lenne Szám típustörlés után, és a metódus metódusra fordítódik érvénytelen halmaz (számtartalom) .
Példa (egy típusparaméter felső határának használatára a típustörlésnél – előtt típusú törlés):
osztályú Box< T kiterjeszti a számot > {
magán T érték;
public Box( T t) { érték = t; }
...
}
- Példa (egy típusparaméter felső határának használatára a típustörlésnél –
után
- típusú törlés):
class Box {
magán Szám érték;
public Box( Szám t) { érték = t; }
...
}
- Ezenkívül a bal szélső felső korlát további helyeken is megjelenik, például automatikusan beillesztett öntvényeknél és áthidaló módszereknél.
Ha engedélyeznék a típusparaméterek alsó határait, milyen mellékhatásokkal járnának vagy kellenek? Ha egy olyan konstrukció, mint pl osztályú Box<T nagy Number> {...} engedélyezett, mit jelentene? Mit jelentene egy felső típusparaméter-korlát 3 mellékhatása – korlátozott példányosítás, hozzáférés a nem statikus taghoz, típustörlés – alsó korlát esetén?
Korlátozott példányosítások. A fordító egy alsó korlátos típusparaméterrel korlátozhatja az általános típus példányosítására használható típusokat. Például a fordító engedélyezhet olyan példányokat, mint pl Doboz<Szám> és Box<Object> a Doboz<T nagy Szám> és elutasítja az olyan példányokat, mint pl Doboz<Long> vagy Doboz<Short> . Ez egy olyan hatás lenne, amely összhangban van a felső típusú paraméterhatároknál leírt korlátozó mellékhatással.
Hozzáférés a nem statikus tagokhoz. Az alacsonyabb típusú paraméter-korlát nem ad hozzáférést semmilyen konkrét metódushoz az osztályból örökölteken kívül Objektum . példájában Doboz<T nagy Szám> a szupertípusai Szám nincs semmi közös bennük, kivéve, hogy referenciatípusok, és ezért altípusai Objektum . A fordító nem feltételezheti, hogy a típusú mező T típusú Szám vagy annak egy altípusa. Ehelyett a típus mező T bármilyen szupertípusú lehet Szám , mint pl Sorozatozható vagy Objektum . Egy olyan módszer meghívása, mint pl intValue() már nem típusbiztos, és a fordítónak el kell utasítania. Következésképpen az alsó típusparaméter-korlát nem ad hozzáférést az osztályban meghatározottakon kívüli nem statikus tagokhoz Objektum és így ugyanaz a hatása, mint a „nincs kötött”.
Írja be: Törlés . Ezt a logikai vonalat követve nincs értelme a típusparaméter előfordulásait a bal szélső alsó korlátra cserélni. Olyan metódus deklarálása, mint a konstruktor Doboz( T t) mint kivitelező Doboz( Szám t) ennek figyelembevételével nincs értelme T szupertípusa váltja fel Szám . An Objektum jogosan átadható a konstruktornak egy példányban Box<Object> és a kivitelező visszautasítaná. Ez halálosan rossz lenne. Tehát a típustörlés a típusváltozó összes előfordulását lecserélné T típus szerint Objektum , és nem az alsó határa szerint. Az alsó határ ismételten ugyanazt a hatást fejti ki, mint a “nincs korlát”.
Szeretné kitalálni, mit jelentene, ha mindkettő alacsonyabb lenne? _és_ megengedettek a felső határok? Én személy szerint nem is akarok erre gondolni, és ha megengedi, szívesebben iktatnám a “nem kezelhető” kategóriába.
A lényeg: minden, ami egy “ nagy A “kötve vásárolna meg” az a korlátozás, amelyre csak a szupertípusok vonatkoznak Szám típus argumentumként használhatók. És még ezt is gyakran félreértik. Ez NEM azt jelentené osztály Box<T szuperszám> {...} csak szupertípusainak példányait tartalmazza Szám . Éppen fordítva – ahogy az alábbi példa is mutatja!
Példa (egy típusparaméter felső határának használatára a típustörlésnél – előtt típusú törlés):
osztályú Box< T szuperszám > {
magán T érték;
public Box( T t) { érték = t; }
...
}
Példa (egy típusparaméter felső határának használatára a típustörlésnél – után típusú törlés):
class Box {
magán Objektum érték;
public Box( Objektum t) { érték = t; }
...
}
A osztályú Box<T nagy Number> {...} típustörléssel fordítanák le a Doboz tartalmaz egy Objektum mezőre és a konstruktorra fordítva lenne Doboz (t tárgy) . Ez alapvetően különbözik a osztályú Box<T kiterjed Number> {...} , amelyet a Doboz amely a Szám mezőre és a konstruktorra fordítva lenne Doboz (t szám) . Következésképpen a Doboz<Szám> példányosított a osztály Box<T kiterjeszti Number> {...} más lenne, mint a Doboz<Szám> példányosítás a osztály Box<T szuperszám> {...} , ami valószínűleg zavart okozhat. Emiatt az osztályok típusparamétereinél nincs értelme az alsó határoknak.
Alsó korlát típusú módszerek paraméterei
A metódusokkal és azok argumentumtípusaival együtt alkalmanként hasznos lehet egy alsó korláttal rendelkező típusparaméter.
Példa (olyan módszerre, amelynél előnyös lenne egy alsó korláttal rendelkező típusparaméter):
osztály Pair<X,Y> {
private X first;
private Y second;
...
public < Egy szuper X , B szuper Y > B addToMap(Map<A,B> térkép) { // hiba: a típusparaméternek nem lehet alsó határa
return map.put(first, second);
}
}
osztály teszt {
public static void main(String[] args) {
Pair<String,Long> pair = new Pair<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Number number = pair.addToMap(map);
}
}
A addToMap() metódus hozzáadja a pár tartalmát egy térképhez. Bármilyen térkép, amely szupertípusokat tartalmazhat X és Y megtenné. A térképé tesz() A metódus az adott kulcshoz tartozó térképen található értéket adja vissza, ha már van kulcsérték bejegyzés a kulcshoz a térképen. A térkép visszatérési értéke tesz() módszert kell visszaküldeni a addToMap() módszer. Ilyen körülmények között szeretnénk deklarálni a fenti módszert: A térkép paraméterezve a pár típusparamétereinek szupertípusaival és a addToMap() A metódus visszatérési típusa a térkép értéktípusa.
Mivel a fordító nem enged alsó határokat a típusparaméterekre, szükségünk van egy kerülő megoldásra.
Az egyik lehetséges megoldás a helyettesítő karakter használata, mivel a helyettesítő karaktereknek lehet alsó korlátja. Íme egy megoldás a helyettesítő karakter használatával.
Példa (az előző példa megkerülésére helyettesítő karakterekkel):
osztály Pair<X,Y> {
private X first;
private Y second;
...
public Objektum addToMap(Map< ? Szuper X , ? szuper Y > térkép) {
return map.put(first, second);
}
}
osztály teszt {
public static void main(String[] args) {
Pair<String,Long> pair = new Pair<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Number number = (Szám) pair.addToMap(map);
}
}
Működik, kivéve, hogy nincs mód a kívánt visszatérési típus deklarálására. Ez lenne a szupertípusa Y amit a fordító a térképtípusból rögzít, de ennek megadására nincs szintaxis. Nem deklarálhatjuk a visszatérési típust a “ ? szuper Y “, mert” ? szuper Y A ” helyettesítő karakter, nem típus, ezért nem engedélyezett visszatérési típusként. Nincs más választásunk, és használnunk kell Objektum ehelyett metódusunk visszatérési típusaként. Ez a meglehetősen nem specifikus visszatérési típus viszont arra kényszeríti a hívókat addToMap() metódust a visszatérési érték lefelé történő leadásához Objektum tényleges típusához. Nem pontosan erre gondoltunk.
Egy másik megoldás a statikus módszerek használata. Itt van egy elkerülő megoldás a statikus módszerrel nem statikus módszer helyett.
Példa (az előző példa megkerülő megoldása statikus módszerrel):
osztály Pair<X,Y> {
private X first;
private Y second;
...
public statikus < A , B ,X kiterjeszti A ,Y kiterjeszti B > B addToMap (Pair<X,Y> pár, Map< A , B > térkép) {
return map.put(pair.first,pair.second);
}
}
osztály teszt {
public static void main(String[] args) {
Pair<String,Long> pair = new Pair<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Number number = Pár .addToMap( pár ,térkép);
}
}
Az általános addToMap() metódusnak négy típusparamétere van: két helyőrző X és Y a pár típusához és két helyőrzőhöz A és B a térkép típusához. A és B szupertípusai X és Y , mert X és Y are declared with A és B mint felső határukat. (Ne feledje, hogy az általános metódus típusparaméterei X és Y semmi köze a Pár osztályé X és Y paramétereket. A nevek X és Y újra felhasználhatók az általános módszerhez, hogy könnyen felismerhetők legyenek a pár típusparamétereiként. Négy típusparaméter segítségével tetszés szerint deklarálhatjuk a pontos visszatérési típust: ez megegyezik a térkép értéktípusával.
Példa (a típusparaméter illegális használatára egy általános osztály statikus kontextusában):
osztály SomeClass <T> {
// statikus inicializátor, statikus mező, statikus módszer
statikus {
SomeClass< T > teszt = új SomeClass< T >(); // hiba
}
magán statikus T globalInfo; // hiba
public statikus T getGlobalInfo() { // hiba
return globalInfo;
}
// nem statikus inicializátor, nem statikus mező, nem statikus módszer
{
SomeClass< T > teszt = új SomeClass< T >();
}
magán T localInfo;
public T getLocalInfo() {
return localInfo;
}
// statikus beágyazott típusok
public statikus osztály A hiba kiterjeszti a kivételt {
private final T info; // hiba
public Failure( T t) { info = t; } // hiba
public T getInfo() { return info; } // hiba
}
magán felület Másolható {
T copy(); // hiba
}
magán enum állam {
VALID, INVALID;
magán T info; // hiba
public void setInfo( T t) { info = t; } // hiba
public T getInfo() { return info; } // hiba
}
// nem statikus beágyazott típusok
public osztály Accessor {
public T getInfo() { return localInfo; }
}
}
A példa azt szemlélteti, hogy a type paraméter nem használható egy általános osztály statikus környezetében. Azt is mutatja, hogy a beágyazott interfészek és enum típusok az osztály statikus típusú tagjainak minősülnek. Csak a belső osztályok, vagyis a nem statikus beágyazott osztályok használhatják a befoglaló általános osztály típusparaméterét.
Az interfész típusparaméterének hatóköre az interfész teljes definíciója, kivéve a mezőket és a beágyazott típusokat. Ennek az az oka, hogy az interfészen meghatározott mezők és beágyazott típusok implicit statikusak.
Példa (a típusparaméter illegális használatára egy általános interfészen):
interfész SomeInterface <T> {
// mező
SomeClass< T > érték = új SomeClass< T >(); // hiba
// beágyazott típus
osztály Accessor {
public T getInfo() { // hiba
return value.getGlobalInfo();
}
}
// mód
T getValue();
}
A példa azt mutatja, hogy egy interfész mezői implicit statikusak, így a típusparaméter nem használható sehol egy általános interfész mezőjének deklarációjában. Hasonlóképpen, a beágyazott osztály statikus beágyazott osztálynak minősül, nem belső osztálynak, és emiatt a type paraméter használata a beágyazott osztályban bárhol illegális.
Megkerülő megoldás a beágyazott statikus osztályokhoz és interfészekhez:
Statikus beágyazott osztályok és interfészek esetén ez nem jelent komoly csapást. Kerülő megoldásként létrehozhatjuk magát a statikus osztályt vagy interfészt.
Példa (megkerülő megoldás – hozza létre a beágyazott statikus típust):
osztályú Burkolat <T> {
private final T theObject;
public Wrapper( T t) { theObject = t; }
public T getWrapper() { return theObject; } public Immutable <T> makeImmutable() {
return new Immutable <T> (az Objektum);
}
public Mutable <T> makeMutable() {
return new Mutable <T> (az Objektum);
}
private static <A> A makeClone(A theObject) { ... }public static final class Immutable <A> { // most egy általános osztály
private final A theObject;public Immutable( A arg) {
theObject = makeClone(arg);
}
public A getWrappedItem() {
return makeClone(theObject);
}
}
public static class Mutable <A> {
... similar ...
}
}
Nincs ilyen megoldás a beágyazott enum típusokhoz, mert nem lehetnek általánosak.
Megoldás beágyazott statikus osztályokhoz:
Ha a beágyazott statikus típus egy osztály, akkor egy alternatív megoldás az lenne, ha a statikus osztályt nem statikus belső osztállyá alakítanák.
Példa (megkerülő megoldás – használjon belső osztályt):
osztályú Burkolat <T> {
private final T theObject;
public Wrapper( T t) { theObject = t; }
public T getWrapper() { return theObject; } public Mutable makeMutable() {
return ez. new Mutable(theObject);
}
public Immutable makeImmutable() {
return ez. new Immutable(theObject);
}
private static <A> A makeClone(A theObject) { ... }public final class Immutable { // már nem statikus osztály
private final T theObject;public Immutable( T arg) {
theObject = makeClone(arg);
}
public T getWrappedItem() {
return makeClone(theObject);
}
}
public class Mutable
... similar ...
}
}
Ez a megoldás bizonyos mértékű többletköltséggel jár, mivel minden belső osztálynak van egy rejtett hivatkozása a külső típusú példányra, amelyre ebben a példában nincs szükségük, és nincs is rá szükségük; a rejtett hivatkozás csak poggyász. A belső osztályokat gyakran interfészekkel kombinálják, hogy a belső osztály a befoglaló osztály privát megvalósítási részlete maradjon. Itt is megtehetjük ugyanezt.
Példa (az előző megoldás finomítva):
osztályú Burkolat <T> {
private final T theObject;
public Wrapper( T t) { theObject = t; }
public T getWrapper() { return theObject; } public Mutable <T> makeMutable() {
return this.new Mutable(theObject);
}
public Immutable <T> makeImmutable() {
return this.new Immutable(theObject);
}
private static <A> A makeClone(A theObject) { ... }nyilvános felület Megváltozhatatlan<S> {
S getWrappedItem();
}
nyilvános felület Mutabilis<S> {
S getWrappedItem();
void setWrappedItem(S arg);
}
magán utolsó osztály ImmutableImplementation Immutable<T> valósítja meg {private final T theObject;
public ImmutableImplementation(T arg) {
theObject = makeClone(arg);
}
public T getWrappedItem() {
return makeClone(theObject);
}
}
magán osztály MutableImplementation megvalósítja a Mutable<T> {
... similar ...
}
}
Mint látható, a beágyazott nyilvános interfészeknek általánosnak kell lenniük, míg a belső privát osztályok használhatják a befoglaló osztály típusparaméterét.

Hagy egy választ