…avagy hogyan lehet pozitív tulajdonság a gyengeség és a lustaság. 🙂
Arról a gyakran előforduló modellről van szó, amikor sorok és oszlopok metszeteiben rácselemek csücsülnek, melyek automatikusan létrejönnek a nekik megfelelő sor-oszlop párra való hivatkozáskor, valamint automatikusan megszűnnek soruk vagy oszlopuk törlése esetén.
A megvalósítás kézenfekvőnek tűnik: a sorokban/oszlopokban egy map-ben tároljuk az oszlopokkal/sorokkal indexelve a rácselemeket.
Valahogy így:
[cc_java]
public class Node {
}
public class Column {
}
public class Row {
protected final Map nodes = new HashMap();
public Map getNodes() {
return nodes;
}
}
[/cc_java]
Mi a gond ezzel?
- Ha törlünk egy oszlopot, az összes sor map-jében még ott marad a rá mutató referencia, mint kulcs. Ez memory leakhez vezet: a szemétgyűjtő nem tudja eltakarítani a “zombi” oszlopokat és a hozzájuk tartozó, ugyancsak zombi rácselemeket. Ez ráadásul az adatstruktúra perzisztálásánál is problémát jelent.
- Hogyan s mikor hozzuk létre a rácselemeket? Mindig, amikor létrehozunk egy oszlopot? Ezt jobban lehetne automatizálni, ráadásul ha nem minden rácspontba akarunk elemet tenni, fölöslegesen hozzuk létre ezeket.
Több megoldás is lehetséges, gondolhatnánk elsőre:
- Gondolkodhatnánk egy címkézett gráfban, és a beépített kollekciók helyett alkalmazhatnánk valamiféle gráfkezelő könyvtárat, pl. a JGraphT-t, mely gondoskodik csúcs törlésekor a hozzá tartozó élekről.
- Kibővíthetnénk a lista adatszerkezetet eseménykezelőkkel, melyek elem hozzáadásakor és törlésekor végrehajtanak egy megadott kódot.
Azonban a probléma megoldható a Java Collections Framework keretein belül, karöltve természetes kiegészítőjével, a Collections Generic API-val (a Commons Collections generikus változata). Nehogy már egy ilyen általános jelenséget ne tudjunk a megannyi hasznos absztrakciót tartalmazó Java nyelv elemeivel modellezni! A kulcsfogalom a gyenge referencia, mellyel befolyásolhatjuk a garbage collector működését, ezzel rábízva az elárvult elemek törlését.
Javában az alapértelmezett referencia erős (strong), de ezenkívül még létezik a gyenge (weak), lágy (soft) és fantom (phantom) referencia. A weak reference lényege, hogy amennyiben egy objektumra csak ilyenek mutatnak, a szemétgyűjtő teljes lelki nyugalommal eltakarítja. Nekünk pont ez a viselkedés jön kapóra: a map-ben gyenge referenciákat fogunk csak tárolni az oszlopokra/sorokra. (A másik két típus most nem érdekes számunkra, pedig azok is hasznosnak bizonyulhatnak más esetekben.)
A Collections API tartalmazza a WeakHashMap osztályt, mely a HashMap-hez hasonló, azonban kulcsai gyenge referenciák. A WeakHashMap ezeket átlátszóan kezeli, önmagától létrehozva és dereferálva nekünk őket. Így programunkban az automatikus törléshez csak két dolgot kell módosítanunk: HashMap helyett WeakHashMap-et használunk, és a kívánt pillanatban meghívjuk a garbage collectort.
A létrehozás pedig lusta map segítségével történik: a rácselemek példányosítása akkor és csak akkor történik, amikor először hivatkozunk az őket azonosító sor-oszlop párosra. Ehhez is kész eszközt kapunk a kezükbe a Collections Generic Libraryben: a LazyMap-et és az InstantiateFactory-t, melyek a Decorator és a Factory pattern alkalmazásával érik el céljukat.
Imhol a forráskód módosított része. Figyelemreméltó, hogy mennyi minden változott…
[cc_java]
public class Row {
protected final Map nodes = LazyMap.decorate(new WeakHashMap(), new InstantiateFactory(Node.class)); // !
}
[/cc_java]
És egy kis tesztosztály, a kiértékelés a kedves olvasó feladata:
[cc_java]
public class GridTest {
public static void main(String[] args) {
Grid grid = new Grid();
for (int i = 0; i < 2; i++) {
grid.getRows().add(new Row());
grid.getColumns().add(new Column());
}
for (Row row : grid.getRows()) {
for (Column column : grid.getColumns()) {
System.out.println(row.getNodes().get(column));
}
}
grid.getColumns().remove(1);
System.gc(); // !
for (Row row : grid.getRows()) {
for (Column column : row.getNodes().keySet()) {
System.out.println(row.getNodes().get(column));
}
}
}
}
[/cc_java]
Hát ilyen power a Collections Framework, amint már Stampie is [intlink id=”587″ type=”post”]rámutatott[/intlink], ami pedig esetleg hiányzik belőle, azt a Collections Generic pótolja. Mi a tanulság? Használjuk ki a nyelvi lehetőségeket, amelyek rendelkezésünkre állnak!