tifyty

pure Java, what else ?

Google ImmutableMap

A Google Guava csomagjában több hasznos dolog is található. Most az ImmutableMap osztályról elménckedem kicsit.

Mi is a mutátor, és az immutable

Egyszerű: mutátor minden olyan metódus, amelyik egy objektum állapotát megváltoztatja. Az objektum immutable, ha nincs olyan metódusa, amelyik meg tudná változtatni, más szavakkal az objektum konstans, nem változik.


Ez nem ugyanaz, mint a final változó. Az csak annyit mond, hogy a változó (már amennyiben változóra használjuk, mert a Java nem feltétlenül szerencsésen ezt a kulcsszót is több, nem egészen ugyanazt jelentő dologra használja), szóval a változó ugyanarra az objektumra mutat. De maga az objektum változhat, és ez az egyik legnagyobb ellenérv a final használata ellen, hogy félrevezetheti az átlag programozót. Könnyen azt lehet hinni, hogy az objektum immutable, pedig nem az.


Miért fontos az, hogy egy objektum megváltozhatatlan legyen? Például vagy egy Map, ami kulcsokhoz rendel értékeket. Persze a kulcs és az érték is objektumok. Ha a kulcs tartalma megváltozik, könnyen előfordulhat, hogy többé nem találjuk meg az elrakott értéket.

import java.util.HashMap;
import java.util.Map;
public class MapExample {
    static class Key {
        String v1;
        Key(String v1) {
            this.v1 = v1;
        }
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((v1 == null) ? 0 : v1.hashCode());
            return result;
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null) return false;
            if (getClass() != obj.getClass()) return false;
            Key other = (Key) obj;
            if (v1 == null) {
                if (other.v1 != null) return false;
            } else if (!v1.equals(other.v1)) return false;
            return true;
        }
    }

    public static void main(String[] args) {
        Map<Key, String> map = new HashMap<Key, String>();
        Key key = new Key("v1");
        map.put(key, "getThisBack");
        key.v1 = "V1";
        System.out.println(map.get(key));
        System.out.println(map.get(new Key("v1")));
        System.out.println(map.get(new Key("V1")));
    }
}

Ez a kis program mind a három sorban azt írja ki, hogy null. Nem kell ezen meglepődni, teljesen korrekt. Az első esetben azért nem találja meg az objektumot, mert nem azzal a hash értékkel lett eltéve, mint amelyikkel keressük. Olyan ez, mint amikor a lámpa alatt keressük a kulcsot, mert ott van világos. Nem ott vesztettük ugyan el, de ha meglátnánk, akkor megismernénk. Csak sajnos esélyünk sincs rá, hogy meglássuk.

A második esetben jó helyen keressük, de valóban nincs benne a map-ben, hiszen már nem ‘v1’ hanem ‘V1’ a kulcsbéli String értéke. A harmadik sor esetén pedig ugyanaz a helyzet, mint az első esetében, nem segít az sem, hogy egy új objektumot hoztunk létre. (A hashCode és equals metódusokat az Eclipse írta, bonyolultabb esetben erre is vannak segítő metódusok a Guava-ban.)

Szóval jó lenne, ha egy Map alatt a kulcsok nem változnának. Persze, tudom, hogy ez egy nagyon szigorú feltétel, és nem kellene ennyire szigorúnak lenni, de amíg nincs az ember tisztában nagyon mélyen a Map rejtelmeivel, addig jobb a szigorúság. És persze utána is, hiszen ki tudja, kinek a kezébe kerül a kód a későbbiekben, és nem várhatjuk, hogy mindenki utolsó regiszterig ismerje a Map-ek lelkivilágát. Még a nőkét sem lehet kiismerni…

És persze az immutabilitás nem áll meg a Map-ek kulcsainál. Általában is jó, ha vannak olyan objektumok, amelyek nem változnak. Például a string-ek, vagy az Integer objektumok, amik ha változnak, igen érdekes dolgok történhetnek. És néha például maguk a Map-ek is ilyenek. Milyen jó lenne, például, ha átadhatnánk egy Map-et egy metódusnak, és biztosak lehetnénk benne, hogy a hívott kód nem változtatja meg a tartalmát. Vagy kapunk a saját metódusunkba egy Map-et, és biztosak lehetünk benne, hogy nem változik meg (például egy másik thread által) amíg mi használjuk.

Miér kell a Guava, miért nem jó az unmodifiableCollection

Az első igen régi igény, már a bronzkorban is felmerült az emberekben, hogy ilyen kellene. Ha pedig valamelyik archeológus affinitással rendelkező programozó elmélyül a Java Collections osztályban, akkor találhat ott olyan metódust, név szerint az unmodifiableCollection és tsi., amelyik olyan nem módosítható nézetet ad vissza, amelyik garantálja, hogy aki csak ehhez fér hozzá, és nem az eredeti gyűjteményhez, az nem fogja tudni megváltoztatni a gyűjteményt.

De ez még nem jelenti azt, hogy valami mágia, vagy csak egy aszinkron thread, vagy egy sima call-back folyamán ne változna meg a gyűjtemény. Amit ez a metódus előállít az csak egy nézet, amelyik minden olyan esetben, amikor módosítani akarjuk EZEN KERESZTÜL az eredeti gyűjteményt elküld egy UnsupportedOperationException körútra. Olvasni is az eredetit olvassuk a nézeten keresztül.

Ennek persze van előnye is. A hátrány, mint mondottam az, hogy nincs garancia, hogy valaki, aki az eredeti gyűjteményhez hozzáfér nem változtatja meg az eredeti gyűjteményt. Az előny, hogy mivel ez csak egy nézet, ezért a saját gyűjtemények — például egy Map implementáció, amelyik alapvetően változtatja meg az emberiség sorsát az újonnan implementált kereső algoritmusunkkal, a HashMap O(1) aszimptotikus időigényű algoritmusát nagyságrendekkel gyorsítva nagy gyűjtemény méretek mellett — algoritmusa a nézeten keresztül is elérhető, és ezt használja a nézet keresés során.

A Guava csomagban található Immutable... osztályok nem ilyenek. Azok nem nézetek, hanem fogják az eredeti gyűjteményt (maradjunk példaképpen a Map-oknál), és létrehoznak egy újat belőle, ami már immutable. Ez gyönyörűen le is van írva ebben a blogban (eredeti meg itt).

Hogyan használjuk?

A kérdés alatt nem a normál “usage”-ra gondolok, arra ott van a man page. Inkább az a kérdés, hogy mikor használjuk?

Háááááttt… ha Java-ban programozunk…

és normális kódot akarunk írni, amelyik nem hasal el. A Guava használata ugyan nem fogja meggátolni, hogy elhasaljon a program, de lecsökkenti az esélyét. Ha átkonvertáljuk a Map-ünket immutable map-pé, és így adjuk át egy metódusnak, akkor még azt a kicsi esélyt is elvesszük a metódusól, hogy álnok módon reflection-nel szerezze meg az eredeti Map-et, amit egy unmodifiableMap esetén még megtehetne. (Mondjuk ez már paranoia szint, mert ha annyira félek, akkor inkább nem is állok neki programozni, hiszen lehet írni olyan JNI modult, amelyik feltúrja az egész heap-et meg stack-et, és bármibe belenyúl.)

Viszont a másik kérdés, hogy a metódusban ImmutableMap argumentumot várjunk-e, vagy jó lesz a sima Map? Általában az a szokásunk, hogy nem az implementációt használjuk az argumentumoknál, hanem az interfészt. Ezt azért tesszük, mert általában nem érdekel minket, hogy milyen algoritmust használ az implementáció, lehet az HashMap vagy TreeMap vagy akármi más (AVL fa, vagy platán), minket csak az érdekel, hogy a Map az Map, lehet belőle kiolvasni, és lehet bele tenni kulcs, érték párokat.

Az ImmutableMap esetében kicsit más a helyzet. Itt ugyanis igen sokszor érdekel, hogy a Map ne változzon amíg a metódus matat körülötte. Végigmegyünk az elemeken, és közben ne jöjjenek létre új elemek, és ne tűnjenek el régiek. Ebben az esetben, logikusan egy java.util.ImmutableMap interfészt kellene használni, amelyiket a java.util.Map kiterjeszt. De ezt nem lehet, több okból is kifolyólag. Az első, hogy ilyen interfész nincs, a többi ok meg ezek után nem érdekes. Anno nem került bele az öröklődési láncba ilyen interfész, és utólag már hiába vágyakozunk rá, hogy legyen nekünk egy gazdag nagypapánk. Ezért kénytelenek vagyunk azt az implementációt használni, ami van: az öreg iszákos nagypapát, aki azért mégiscsak szereti az unokáját: ez a com.google.common.collect.ImmutableMap.

Ha viszont mi hívunk egy metódust, amelyik sima Map-et vár, akkor nem biztos, hogy az ImmutableMap a legjobb megoldás. Ebben az esetben elegendő, és kevesebb erőforrást igényel ha a unmodifiableMap metódussal hozunk létre egy nézetet, és megbízunk annyira a hívott metódusban, hogy nem tesz gonosz dolgokat.

És akkor még mindig ott a kérdés, hogy mi gátolja meg, hogy maguk a kulcs és érték objektumok megváltozzanak?

7 responses to “Google ImmutableMap

  1. tv1k augusztus 21, 2013 12:42 du.

    Mutable kulcs használata több mint gyanús. Talán valami közelítő statikus kódanalizátor szabályt lehet rá írni, de legkésőbb a figyelmes kódreviewer kell hogy kiszúrja.

    Map vagy ImmutableMap esetén érték (úgy értem érték belső állapot) megváltoztatása nem kellene hogy gond legyen, kívül esik a Map/ImmutableMap felelősségi hatáskörén.

    • Peter Verhas augusztus 21, 2013 12:49 du.

      Map vagy ImmutableMap esetén érték (úgy értem érték belső állapot) megváltoztatása nem kellene hogy gond legyen, kívül esik a Map/ImmutableMap felelősségi hatáskörén.

      Az igaz, hogy nem a map felelőssége, mert ezzel a map nem igazán tud mit kezdeni, és mint az életben nem érdemes olyan entitást — legyen az ember, vagy Java objektum — felelőssé tenni valamiért, amire az nincsen hatással. Ahogy általában nem vagy felelős például azért, ha leukémiás leszel. De azért még gond.

      • tv1k augusztus 21, 2013 1:38 du.

        Ezt a leukémiás párhuzamot nem teljesen értem. Egy betegség legtöbbször nem valaki szándékos választása, de a kód legtöbbször igen.

        A Map-nál az érték megváltoztatása onnantól gond, ha mi magunk gondot csinálunk belőle.

        • Peter Verhas augusztus 21, 2013 1:44 du.

          Általában nincs az egész kód a kezedben. Valaki, valahol a hívási sor alján, húsz hívással mélyebben megváltoztatja, és bajban vagy. Nem a te felelősséged, hanem azé, aki a szar kódot készítette? Elvileg igen. De a tied is, mert ezt a library-t választottad. És az ő gondja? Nem. A Te gondod, mert a Te programod nem működik, és az ügyfelet nem érdekli, hogy miért nem megy. Menjen! Az ő ügyfeleit sem érdekli, hogy miért nem működik jól az informatikai rendszer, és azok ügyfeleit sem, és így tovább. Neked kell megoldani. A leukémiai is ilyen. Ki tehet róla? Érdekes? Nem. Neked kell megoldani, ha beteg vagy mert ha nem, akkor Te pusztulsz el.

          A programozás persze nem ilyen végletes. Lehet azt mondani, hogy a dolog legyen az ügyfél problémája: meg fogja oldani. De akkor a te problémád viszont az lesz, hogy mivel fizetsz a péknek a kenyérért.

          • tv1k augusztus 21, 2013 3:08 du.

            Attól hogy egy Map-ban az érték mutable objektum, még nem biztos hogy szar a kód (persze az Immutable objektumok használata előnyösebb). A lényeg, hogy minden résztvevő tudatában legyen, hogy mi volt a döntés. A “mi magunk”-at úgy értettem, hogy a fejlesztő csapat. Mi döntjük el, illetve valami architect ember talán, de ez részletkérdés. Eldönthetjük, szemben a kulcssal, amit ha valaki megváltoztat, explicit rázza a pofonfát.

            • Peter Verhas augusztus 21, 2013 3:10 du.

              Igen, ez pontosan így van. Az értékek változhatnak, csak a kulcsok ne változzanak lényegesen. Nem fogalmaztam talán elég pontosan, hiszen a cikk végén az értékeket is említettem, nem csak a kulcsokat.

  2. tamasrev augusztus 22, 2013 8:24 de.

    Az immutable map forrását még nem olvastam el (de most már el fogom). Addig is, két tipp a mutable kulcsok ellen:
    1. olyat kell írni, ami immutable
    2. Ha a kollegák ezt nem értik, akkor az immutable map a keySet() / entrySet() hívásokon keresztül adhatna másolatot a kulcsra, amit aztán bazirgálhat a kliens kód.. De inkább írjunk immutable kulcsokat, és ezt ellenőriztessük le valamelyik kódanalizátorral.

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s

%d blogger ezt kedveli: