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.