A Java Generics alapjai
1. Bemutatkozás
A Java Generics-et a JDK 5.0-ban vezették be azzal a céllal, hogy csökkentse a hibákat, és egy extra absztrakciós réteget adjon a típusokhoz.
Ez a cikk egy gyors bevezetés a Generics Java programba, a mögöttük álló cél és annak felhasználása a kódunk minőségének javítására.
2. A generikumok szükségessége
Képzeljünk el egy forgatókönyvet, ahol a Java listát szeretnénk létrehozni a tároláshoz Egész szám; kísértésbe eshetünk az írással:
Lista lista = new LinkedList (); list.add (új egész szám (1)); Egész szám i = list.iterator (). Következő ();
Meglepő módon a fordító az utolsó sorra panaszkodik. Nem tudja, milyen adattípus kerül visszaadásra. A fordítónak kifejezett átküldésre lesz szüksége:
Egész i = (Egész) lista.iterator.next ();
Nincs olyan szerződés, amely garantálhatná, hogy a lista visszatérési típusa egy Egész szám. A definiált lista bármilyen objektumot tartalmazhat. Csak azt tudjuk, hogy egy listát a kontextus vizsgálatával kapunk le. A típusok vizsgálatakor csak garantálni tudja, hogy ez egy Tárgy, ezért kifejezett szereplőkre van szükség annak biztosításához, hogy a típus biztonságos legyen.
Ez a szereposztás bosszantó lehet, tudjuk, hogy a lista adattípusa egy Egész szám. A szereplők is összezavarják a kódunkat. Típussal kapcsolatos futásidejű hibákat okozhat, ha a programozó hibát követ el a kifejezett átküldéssel.
Sokkal könnyebb lenne, ha a programozók kifejezhetnék szándékukat bizonyos típusok használatára, és a fordító biztosíthatja az ilyen típusok helyességét. Ez a generikumok alapgondolata.
Módosítsuk az előző kódrészlet első sorát:
Lista lista = new LinkedList ();
A típust tartalmazó gyémánt operátor hozzáadásával a lista specializációját csak a következőkre szűkítjük Egész szám típus, azaz megadjuk azt a típust, amely a listán belül lesz. A fordító kényszerítheti a típust fordításkor.
Kis programokban ez triviális kiegészítésnek tűnhet, azonban nagyobb programokban ez jelentős robusztusságot adhat, és könnyebben olvashatóvá teszi a programot.
3. Általános módszerek
Az általános módszerek azok a módszerek, amelyeket egyetlen metódusdeklarációval írnak, és különböző típusú argumentumokkal hívhatók meg. A fordító biztosítja a használt típusok helyességét. Ezek az általános módszerek néhány tulajdonsága:
- Az általános módszereknek van egy típusú paraméterük (a típust becsatoló gyémánt operátor) a metódus deklaráció visszatérési típusa előtt
- A típusparaméterek korlátozhatók (a határokat a cikk későbbi részében fejtjük ki)
- Az általános módszerek különböző típusú paramétereket vesszővel elválaszthatnak a metódus aláírásában
- A módszer törzse egy általános módszerhez hasonló, mint egy normál módszer
Példa egy általános módszer meghatározására egy tömb listává alakításához:
public List fromArrayToList (T [] a) {return Arrays.stream (a) .collect (Collectors.toList ()); }
Az előző példában a a metódusban az aláírás azt jelenti, hogy a módszer generikus típussal fog foglalkozni T. Erre akkor is szükség van, ha a módszer érvénytelen.
Mint fent említettük, a módszer egynél több általános típussal is képes foglalkozni, ahol ez az eset áll fenn, minden általános típust hozzá kell adni a metódus aláírásához, például ha a fenti módszert módosítani akarjuk a típus kezelésére T és írja be G, így kell írni:
public static List fromArrayToList (T [] a, Function mapperFunction) {return tömbök.stream (a) .map (mapperFunction) .collect (Collectors.toList ()); }
Olyan függvényt adunk át, amely egy tömböt átalakít a típus elemeivel T típusú elemekkel felsorolni G. Ilyen például a konvertálás Egész szám annak Húr reprezentáció:
@Test public void givenArrayOfIntegers_thanListOfStringReturnedOK () {Egész szám [] intArray = {1, 2, 3, 4, 5}; Lista stringList = Generics.fromArrayToList (intArray, Object :: toString); assertThat (stringList, hasItems ("1", "2", "3", "4", "5"); }
Érdemes megjegyezni, hogy az Oracle ajánlása egy nagybetű használata egy általános típus képviseletére, és a formális típusok képviseletére egy leíróbb betű választása, például a Java gyűjteményekben T típusra használják, K kulcsért, V az értékért.
3.1. Korlátozott generikusok
Mint korábban említettük, a típusparaméterek korlátozhatók. A határolt azt jelenti:korlátozott„, Korlátozhatjuk a módszerrel elfogadható típusokat.
Például meghatározhatjuk, hogy egy metódus elfogadja a típust és annak összes alosztályát (felső határ), vagy egy típust az összes felső osztálya (alsó határ).
A felső határolt típus deklarálásához a kulcsszót használjuk kiterjed a használni kívánt felsõ határ követõ típus után. Például:
public List fromArrayToList (T [] a) {...}
A kulcsszó kiterjed itt azt jelenti, hogy a típus T kiterjeszti a felső határt osztály esetén, vagy megvalósítja a felső határt interfész esetén.
3.2. Több határ
Egy típusnak több felső határa is lehet az alábbiak szerint:
Ha a kibővített típusok egyike T osztály (azaz Szám), a korlátok listáján elsőnek kell lennie. Ellenkező esetben fordítási időbeli hibát okoz.
4. Helyettesítő karakterek használata a generikusokkal
A helyettesítő karaktereket a Java kérdőjel képviseli.?”És ismeretlen típusra utalnak. A helyettesítő karakterek különösen hasznosak generikus gyógyszerek használatakor, és paramétertípusként használhatók, de először is van egy fontos megfontolandó jegyzet.
Ismert tény Tárgy az összes Java osztály felső típusa, azonban a Tárgy nem egyetlen kollekció csúcstípusa.
Például a Lista nem a szupertipusa Lista és egy típusú változó hozzárendelését Lista típusú változóra Lista fordító hibát okoz. Ezzel megakadályozzuk az esetleges konfliktusokat, amelyek akkor fordulhatnak elő, ha heterogén típusokat adunk ugyanahhoz a gyűjteményhez.
Ugyanez a szabály vonatkozik bármely típusú gyűjteményre és annak altípusaira. Tekintsük ezt a példát:
public static void paintAllBuildings (List building) {buildings.forEach (Building :: paint); }
ha a altípusát képzeljük el Épületpéldául a Ház, nem használhatjuk ezt a módszert a Ház, annak ellenére Ház altípusa Épület. Ha ezt a módszert a Building típus és annak minden altípusa között kell használnunk, akkor a körülhatárolt helyettesítő karakter varázsolni tudja:
public static void paintAllBuildings (Épületek listája) {...}
Ez a módszer a típussal fog működni Épület és minden altípusa. Ezt nevezzük felső határolt helyettesítő karakternek, ahol típus Épület a felső határ.
A helyettesítő karakterek alsó határral is meghatározhatók, ahol az ismeretlen típusnak a megadott típusú szupertípusnak kell lennie. Az alsó határokat a szuper kulcsszó, amelyet az adott típus követ, például jelentése ismeretlen típus, amelynek szuperosztálya T (= T és minden szülője).
5. Írja be a Törlés parancsot
Generikusok kerültek a Java-ba a típusbiztonság biztosítása és annak biztosítása érdekében, hogy a generikusok ne okozzanak rezsit futás közben, a fordító az ún. típusú törlés a generikumokról a fordítás idején.
A Type erasure eltávolítja az összes típusparamétert, és helyettesíti azokat a határaikkal vagy azokkal Tárgy ha a type paraméter nincs korlátozva. Így a fordítás utáni bájtkód csak normál osztályokat, interfészeket és módszereket tartalmaz, ezzel biztosítva, hogy ne állítsanak elő új típusokat. Megfelelő öntést alkalmaznak a Tárgy írja be fordításkor.
Ez egy példa a törlésre:
public List genericMethod (List list) {return list.stream (). collect (Collectors.toList ()); }
Típus törléssel a korlátlan típus T helyébe a következő lép: Tárgy alábbiak szerint:
// szemléltetés céljából nyilvános List withErasure (List list) {return list.stream (). collect (Collectors.toList ()); } // amely a gyakorlatban nyilvános List withErasure (List list) -et eredményez {return list.stream (). collect (Collectors.toList ()); }
Ha a típus be van határolva, akkor a típust a kötés helyettesíti a fordítás idején:
public void genericMethod (T t) {...}
az összeállítás után megváltozna:
public void genericMethod (t épület) {...}
6. Általános és primitív adattípusok
A generikumok korlátozása a Java-ban az, hogy a type paraméter nem lehet primitív típus.
Például a következő nem fordítja le:
Lista lista = new ArrayList (); lista.add (17);
Emlékezzünk arra, hogy megértsük, miért nem működnek a primitív adattípusok a generikumok fordítási idejű jellemzők, ami azt jelenti, hogy a type paraméter törlődik, és az összes általános típus típusként valósul meg Tárgy.
Példaként nézzük meg a hozzá egy lista módszere:
Lista lista = new ArrayList (); lista.add (17);
A. Aláírása hozzá módszer:
logikai adalék (Ee);
És összeállítják:
logikai add (e objektum);
Ezért a típusparamétereknek konvertálhatónak kell lenniük Tárgy. Mivel a primitív típusok nem terjednek ki Tárgy, nem használhatjuk őket típusparaméterként.
A Java azonban dobozos típusokat kínál a primitívek számára, valamint az autoboxing és az unboxing kibontásához:
Egész a = 17; int b = a;
Tehát, ha olyan listát akarunk létrehozni, amely egész számokat tartalmazhat, használhatjuk a burkolót:
Lista lista = new ArrayList (); lista.add (17); int első = list.get (0);
Az összeállított kód a következők egyenértékű lesz:
Lista lista = new ArrayList (); list.add (Integer.valueOf (17)); int első = ((Egész) lista.get (0)). intValue ();
A Java jövőbeli verziói primitív adattípusokat engedélyezhetnek a generikusok számára. A Valhalla projekt célja a generikumok kezelésének javítása. Az ötlet a generikus specializáció megvalósítása a 218. számú JEP-ben leírtak szerint.
7. Következtetés
A Java Generics a Java nyelv hatékony kiegészítése, mivel megkönnyíti a programozó munkáját és kevésbé hajlamos a hibára. A generikusok kikényszerítik a típushelyességet a fordítás idején, és ami a legfontosabb, lehetővé teszik az általános algoritmusok megvalósítását anélkül, hogy különösebb rezsit okoznának alkalmazásainknak.
A cikkhez tartozó forráskód elérhető a GitHubon.