Jelenlegi hely

15. Általános programozás

Az általános (generikus) programozás típusparaméterek segítségével teszi lehetővé, hogy osztályokat, interfészeket hozhassunk létre úgy, hogy bizonyos paraméterek típusait a példányosításkor dönthessük el.

Általános típus definiálása és használata

Ahhoz, hogy általános típust definiáljunk, a típusdefiníciónak tartalmaznia kell egy vagy több típus paramétert a típus neve után. A típus paraméterek vesszővel elválasztott listái ’<’ és ’>’ között szerepelnek. Konvencionálisan a típus paraméterek nagybetűsek. A típus paraméterek aztán megjelennek a típus metódusaiban, vagy a metódus paraméterlistájában, vagy visszatérési érték típusként. A gyakorlatban a legtöbb fejlesztőnek nincs szüksége új általános típusok definiálására, de szükséges megjegyezni a szintaxisát és a használatát az általános típusnak.

Nézzük újra át a Stack osztályt, melyet az osztályok létrehozásánál mutattunk be. A generikus verzió (Stack2<T>), egy gyűjteményt használ (ArrayList<T>), hogy a veremben tárolja az érékeket.

public class Stack2<T> {
    private ArrayList<T> items;
    ...
    public void push(T item) {...}
    public T pop() {...}
    public boolean isEmpty() {...}
}

Vegyük észre, hogy a T típus paraméter bevezetésre került az osztálynév után, és ezek után feltűnik, mint a push metódus paraméter típusa, és a pop metódus viszszatérési típusa.

A gyűjtemények gyakran használatosak arra, hogy bemutassuk az általános típus használatát, mivel nagyon jellemzőek az interfészekben és osztályokban. A valóságban a gyűjtemények voltak a fő motivációs erő az általános típusok bevezetésénél a Java nyelvben, mivel elérhetővé teszik a fordítás idejű ellenőrzését a gyűjteményeken végzett műveletek típusbiztonságának. Amikor specifikáljuk egy gyűjteményben tárolt objektum típusát:

  • a fordító tud ellenőrizni bármilyen műveletet, ami egy objektumot ad a gyűjteményhez
  • ismert az objektum típusa, mely a gyűjteményből lett kinyerve, ezért nincs szükség arra, hogy cast-oljuk (átalakítsuk) típussá. Ugyanakkor nincs lehetőség arra sem, hogy átalakítsunk egy rossz típussá, és ekkor megtapasztaljunk egy futás idejű ClassCastException kivételt.

Ahogy az előbbiekben írtuk, ha egy általános típust használunk, helyettesítjük a paraméter egy aktuális típus paraméterét, nagyjából ugyanezen módszerrel helyettesítünk egy metódushoz a paramétereihez tartozó aktuális értékeket. Egy aktuális típus paraméternek referencia típusnak kell lenni, nem lehet primitív. Például, itt látható az, hogy hogyan lehet létrehozni Stack2-t String típus paraméterrel; és ezek után push-olni és pop-olni a „hi” String-et.

Stack2<String> s = new Stack2<String>();
s.push("hi");
String greeting = s.pop(); //no cast required here

Egy nem generikus verem kód így nézne ki:

Stack s = new Stack();
s.push("hi");
String greeting = (String)s.pop(); //cast required here

Vegyük észre, hogy amikor az általános típust használjuk, akkor a fordító egy olyan technikával fordítja le az általános típust, amit típus törlésnek hívnak.

Gyakorlatilag a fordító törli az összes olyan információt, mely a típus paraméterrel, vagy a típus paraméterekkel kapcsolatos. Például a Stack2<string> típust lefordítja Stack2-re, melyet nyers típusnak neveznek. Abból következtethetünk a típus törlésre, hogy a típus paraméter nem érhető el futásidőben ahhoz, hogy használjuk típuskényszerítésben, vagy mint az instanceof eljárás paramétere.