Gyakorlati alapok III.
A generikus (általános) típus
A Java erősen típusos nyelv, ezt azt jelenti, hogy a rendszer folyamatosan figyeli, ellenőrzi és ha tudja, konvertálja a típusokat. Ugyanakkor a típusegyeztetések szigorú szabályok szerint történnek, ezen sajátossága egyébként fontos része a biztonsági szempontoknak.
Mindezek felett azonban arra is lehetőségünk van, hogy ne konkrét típust, hanem egy általános típust adjunk meg és a konkrét típust (az általánosan megadott típus paraméterei alapján) csak példányosítás során határozzuk meg.
Milyen körülmények közt használhatjuk ezt fel?
-
Garantáltan többféle adattípussal, de hasonló jellegű metódusokkal akarunk dolgozni,
-
az általános típus –elvi szinten hasonlóan az interfészekhez-, a lehető legáltalánosabban, mintegy “filozófiai” szinten közelít a programozási alapelvekhez. Ez nehezebben érthető, de végső megfogalmazásában tömörebb kódot eredményez.
Az általános típust konvencionálisan a <> közé írt nagybetűvel jelöljük, például:
<T>
Sok helyen használják ehhez a T nagybetűt, de más betűkkel, sőt szavakkal is működőképes, én eddig a következőkkel találkoztam:
<E>
<G>
<Type>
Ezidáig mindez kissé (nagyon) érthetetlen, ezért ugorjunk is bele 1. példakódunkba!
Az általános típus felhasználásával alkossunk egy olyan futtatható Java-kódot, amelyik egyaránt képes egy Integer és egy String típusú tömb elemeinek listázására!
Ehhez először egy általános típusú metódust implementálunk:
public static <T> void tombElemListazas(T[] tomb) {
for(T elem : tomb) {
System.out.print(elem + " ");
}
System.out.println();
}
Ez azt jelenti, hogy ez a metódus univerzálisan képes lesz többféle típusú tömb listázására. Enélkül minden egyes típusra külön-külön még kéne írni a kódot. A konkrét típus a tömb létrehozásakor fog eldőlni.
Láthatjuk, hogy a foreach ciklus megadásakor szintén az általános típus-meghatározásokat használtunk fel, illetve nemcsak a metódus, hanem a belső adatok is általános típusúak.
public class Main {
public static <T> void tombElemListazas(T[] tomb) {
for(T elem : tomb) {
System.out.print(elem + " ");
}
System.out.println();
}
public static void main(String args[]) {
Integer[] tombInteger = {1, 2, 3};
String[] tombString = {"1", "2", "3"};
System.out.println("Integer tömb elemei:");
tombElemListazas(tombInteger);
System.out.println("String tömb elemei:");
tombElemListazas(tombString);
System.out.println();
System.out.println(tombInteger[0] + tombInteger[1]);
System.out.println(tombString[0] + tombString[1]);
}
}
Végeredmény:
Integer tömb elemei:
1 2 3
String tömb elemei:
1 2 3
3
12
Az általános típus felhasználásakor tehát előzetesen mindig el kell gondolkodnunk arról, hogy a kívánt műveletek működőképesek-e többféle típuson is, azaz a művelet általánosítható-e. Látjuk, hogy a tömbelemek listázása pontosan ilyen művelet.
Vegyük észre, hogy a kód végén, a példányosítások után érdekes kísérletet teszünk: felhasználjuk a + jellel illetett műveletet:
System.out.println(tombInteger[0] + tombInteger[1]);
System.out.println(tombString[0] + tombString[1]);
Ez Integer típus esetén összeadás lesz (1 + 2 = 3), String esetében viszont összefűzés, konkatenáció ("1" + "2" = "12").
A Java-rendszer azonban olyan intelligens: bárhogy is próbáljuk, ezt a műveletet semmiféleképpen sem tudjuk az általános típusú metódusrészbe gyömöszölni, hiszen ez a művelet a különböző típusokra másképpen értelmezhető, tehát nem általánosítható.
Az általános típus nemcsak metódusokra, hanem adattagokra és osztályokra is
alkalmazható. Az alábbi futtatható Java-kódban mindhárom komponens általános
típusmeghatározást kap. Újfent csakis olyan műveleteket választhatunk ki,
amelyek általánosíthatóak, ilyen például egy objektum adatokkal való
feltöltése (hozzaad()) és adatlekérdezés (getAdat()).
public class Main<T> {
private T adat;
public void hozzaad(T adat) {
this.adat = adat;
}
public T getAdat() {
return adat;
}
public static void main(String[] args) {
Main<Integer> taroloInteger = new Main<Integer>();
Main<String> taroloString = new Main<String>();
taroloInteger.hozzaad(new Integer(1));
taroloString.hozzaad(new String("Ez egy sztring."));
System.out.println(taroloInteger.getAdat());
System.out.println(taroloString.getAdat());
}
}
Végeredmény:
1
Ez egy sztring.
Tanítványi kérdés: a generikus típus felhasználható-e, ha nem látjuk előre a feldolgozandó adat típusát, például várjuk a felhasználótól?
Természetesen attól, hogy nem ismerjük a bekért adatot, igenis ismernünk kell
annak típusát. Sőt, arra is fel kel készülnünk, hogy a felhasználó ártó
vagy ártatlan szándékból rossz adatot gépel be (például szám helyett betűt). A
program bemeneti adatait, vele a felhasználó minden mozdulatát (gondoljunk egy
rossz egérkattintásra) tehát validáló (ellenőrző) rutinok segítségével teljes
kontroll alatt kell tartanunk. Ezen probléma kezelésére az általános típus nem
alkalmas.
Az általános típust sok egyéb helyen tudjuk felhasználni, de
kollekciók
esetében kiemelten hasznosnak fog bizonyulni ez a lehetőség.