tifyty

pure Java, what else ?

Havi archívumok: június 2013

Szünet

Most nyári szünet következik a blog életében. Nincs időm új bejegyzéseket írni, meg szabadságra is megyek egy kicsit, és amúgy is azt vettem észre, hogy a melegben a kommentelés erősen visszaesett. Persze bizonyára sokan olvassák a blogot, csak nem írnak kommentet, de azt gondolom, hogy azért a két dolog korrelál (vagy összefügg ?).

Ötleteim vannak, hogy miről fogok írni. Például birkákról …

Csak így off:topic, hogy a gondolatmenet stack hagyományát megőrizzem egy nem szakmai bejegyzésben is: meglepve láttam pár hete, hogy egy Zürich melletti farmon lámákat tartanak. Állítólag nem ritka, és hát miért ne?

Ahogy kezdem egyre jobban megérteni a GIT lelkivilágát valószínűleg arról is fogok írni. Persze angolul van már ezer cikk róla, meg mindenről, de majd jól megbeszéljük. Időpontot, pontosat most nem ígérek, ahogy a MÁV sem ígért semmit, amikor legutóbb vonattal utaztam vissza. Csekély 5 órát késett a vonat a 13 órás úthoz képest, ami autóval egyébként is csak 10 óra lenne…

“Menj! Lőjetek sört!”

Folyós api

Van egy library, amiben vannak osztályok, és vannak metódusaik. Ezeket használjuk. Például van egy olyan metódus, amelyik egy stringről leveszi a nyitó és záró idézőjeleket (mert mondjuk a Java annotációs api így adja vissza a String annotáció értékét, hogy az elején és a végén ott van egy ". Akkor lehet valami ilyen:

final String bareType = StringTool.unquote(quoted);

Aztán arra is szükségünk lehet, mert például annotációkat kezelgetünk, meg java forrást generálunk, hogy ha a string (most már idézőjelek nélkül) egy Java osztály neve, és speciel a java.lang csomagban van, akkor le is szedhetnénk a string elejéről a java.lang. prefixet. Erre is van egy metódus. Most már így néz ki a kódunk:

final String unquoted = StringTool.unquote(quoted);
final String bareType = StringTool.unJavaLangized(unquoted);

Ja, csak éppen elfelejtettük, hogy a stringben pontok helyett a csomagnevek között - jelek vannak. Ezt is át kell konvertálni mielőtt levesszük a prefixet, de szerencsére erre is van egy metódus. Nosza editáljuk meg az előző példát:

final String unquoted = StringTool.unquote(quoted);
final String dotted = StringTool.dashDotConvert(unquoted);
final String bareType = StringTool.unJavaLangized(unquoted);

És kész is. Persze nem működik, és nem is szép. Mennyivel szebb lenne valahogy így:

final Strign bareType = FromThe.string(quoted).unquote()
                             .dashDotConvert().unJavaLangized().get();

És nem csak olvashatóbb, de működik is. Ez már félig fluent API, de még nem teljesen az. Ezt ne felejtsük el, és erre még visszatérek később. Az előző példa viszont azért nem működik, mert el lett gépelve: és milyen könnyű elgépelni, és milyen könnyű utána ezt nem észrevenni.

Most nézzük meg, hogy hogyan kell egy ilyen API-t megvalósítani.

A FromThe osztályban van egy static metódus, ami létrehoz egy StringTool objektumot. Ez az osztály a konstruktorában fogad egy String-et, amit jól eltesz egy final StringBuilder field-be. Valahogy így:

class FromThe {
  StringTool string(String s){
    return new StringTool(s);
    }
  }

class StringTool {
  final StringBuilder s;
  StringTool(String s){
    this.s = new StringBuilder(s);
    }
  }

Utána a unquote, dashDotConvert és unJavaLangized metódusok a belső mezőn dolgoznak, és a this értékét adják vissza, majd a get() metódus visszaadja a string építőben összerakott karaktereket String-ként.

De mi van akkor, ha rosszul használjuk az API-t, és

final Strign bareType = FromThe.string(quoted).unquote()
                             .unJavaLangized().get();

formában használjuk? Ha előbb nem, futás során kiderül. De miért nem derül ki előbb? Miért ne lehetne, hogy készítsünk a StringTool osztály mellé egy Unquoted, DashDotConverted interfészt, amik deklarálják a dashDotConvert és unJavaLangized metódusokat? A metódusok pedig továbbra is a this értékét adják vissza, de metódusok visszatérési típusa nem StringTool hanem valamelyik interfész, amelyek mindegyikét természetesen implementálja a StringTool osztály.

class FromThe {
  Quoted string(String s){
    return (Quoted)new StringTool(s);
    }
  }

interface Quoted { Unquoted unquote(); }
interface Unquoted { DashDotConverted dashDotConvert(); }
interface DashDotConverted { DashDotConverted unJavaLangized(); String get();}

class StringTool implements Quoted, Unquoted, DashDotConverted {
  final StringBuilder s;
  StringTool(String s){
    this.s = new StringBuilder(s);
    }

  // method implementations

  }

Ekkor már a fenti hiba fordítási időben kiderül a

    FromThe.string(quoted).unquote()

típusa Unquoted, az az interfész pedig nem definiálja az unJavaLangized() metódust, az IDE már húzza is alá pirosan. Ez már alakul, ezt már hívhatjuk fluent API-nak. (Ha esetleg valakinek nem esett le, hogy a példa meglehetősen egyszerű, és csak mintapélda és esetleg azt gondolná, hogy ez egy overkill, akkor ajánlom figyelmébe a JOOQ (ejtsd dzsók, mint az angol joke szó) API-t, és ha még nem találkozott fluent API-val, akkor próbálja megérteni azon keresztül, és ha nem sikerült, akkor örömmel látom újra itt ennél a cikknél megérteni az alapokat.)

Érdemes észrevenni, hogy a unJavaLangized visszatérési típusa is DashDotConverted. Előfordulhat ugyanis, hogy a nem akarjuk unjavalangizálni a stringet, ezért a get() metódusnak benne kell lennie a DashDotConverted interfészben, és ugyan lehetne egy új interfész ami csak a get() metódust tartalmazza, és ami a unJavaLangized visszatérési típusa lehetne, de felesleges, mert a unJavaLangized metódus idempotens. (Hú, wazze, jó lett volna odafigyelni az iskolában!)

Mi történik akkor, ha szükségünk van a hosszú és a rövid formára is?

final DashDotConverted converted = FromThe.string(quoted).unquote()
                                                     .dashDotConvert();
final String shortClassName = converted.unJavaLangized().get();
final String longClassName  = converted.get();

Röviden: pofáraesés van. Mind a két eseten a rövid verziót kapjuk. Miért? Mert a metódusaink megváltoztatják az egyetlen StringTool objektumunk állapotát. Mit lehet tenni? A junior megoldás szerint először a hosszú nevet kell leírni. (ha ha ha) Na de nem azért tervezünk fluent api-t, hogy utána mindenféle mellékhatásokat kelljen a használónak figyelembe vennie.

Mi lenne, ha inkább minden egyes metódusunk úgy lenne implementálva a StringTool metódusban, hogy a megváltozott String-et nem helyben teszi el, hanem egy új StringTool objektumot hoz létre, és abban lesz a megváltoztatott string, és azt adja vissza? Ezzel ugyan megoldjuk a fenti problémát, de egyre több, rövid életű objektumot generálunk. Ez viszont legyen a JVM meg a java fordító gondja: a Java 7 már fordítási időben kioptimalizálja ami nem szökik ki a metódusból, és nem is a heap-en allokálja: amikor visszatér a metódus az objektum GC nélkül begyűjtődik.

Az élet szép és jó. Egy kicsit ugyan zavaró, hogy sok sablon kódot írunk, főleg az a rész, hogy minden egyes metódusunk létrehoz egy új StringTool példányt. Ez mindenhol tök egyforma, és így minden metódus két dolgot csinál: ami a funkciója, plusz még “klónozza” az aktuális objektumot. De nincs “demanding need”, hogy változtassunk, és lustán megelégszünk ezzel az eljárással, és programozásai pattern-nel.

Aztán jön az újabb ügyféligény, hogy az kellene, hogy a pontok helyett dash karaktereket tartalmazó csomag+osztály nevet is meg kellene tudnunk szabadítani a java-lang prefix-től. Megoldás?

Írjuk át a unJavaLangized metódust. És ebben a pillanatban történik a tragédia. És akkor egy kis kitérő:

Amikor API-t tervezünk figyelembe kell vennünk, hogy hogyan lehet az API-t használni, hogy hatékonyan lehessen programozni. Ez azonban másodlagos. Elsősorban azt kell jó alaposan meggondolnunk, hogy hogyan fogják az api-t rosszul használni, abuzálni. Elferdítve Murphy törvényét: amit el lehet, azt el fogják.

A tragédia az, hogy tisztelt API használó, aki maga is programozó, lustaságból kihasználta, hogy unJavaLangized idempotens. Volt olyan metódusa, amelyik már rövidített nevet kapott néha, máskor meg nem, és az egyszerűség kedvéért meghívta rá a unJavaLangized metódust. Most viszont ez a metódus megszűnt idempotens lenni, például a java.lang.java-lang-java.lang. stringet három unJavaLangized() hívás eltünteti. Ez ugyan csak ritkán okoz problémát, de aki egy ideje programoz az pontosan tudja, hogy azt a bugot könnyű elkapni ami gyakran okoz problémát, és azok, amelyek csak a hold a nap és a csillagok együttállásakor jelennek meg akasztott ember árnyékában örök életűek. Ezért a “csak ritkán okoz problémát” nem érv, főleg amikor a lélegeztető gép szoftverhibája a te gyereked műtétje közben gondol úgy, hogy akcióba kell lépnie. (Jobb, ha megnézed mielőtt a pacemakert beültetik, hogy nincs-e rajta Made in China felirat. Én szerencsére csak egy svájci hátizsákkal jártam így. Minőség! Hahh!)

Jobb, ha inkább csinálunk egy dashUnJavaLangized metódust, és mindegyik csinálja a saját dolgát. Viszont az ügyfél pampog, hogy ez így neki nem jó, az API maga ne változzon. Na jó, akkor legyen a unJavaLangized univerzális, és csináljuk meg, hogy úgy legyen idempotens, hogy megjegyezzük, hogy már egyszer végre lett hajtva. Gányolgatunk? Ja. Valójában a string mellé, ami az egyetlen változó volt, ami az api végrehajtás állapotát reprezentálta, most felvettünk még egy változót (feltehetően boolean-t), és minden egyes metódusban gondoskodnunk kell, hogy ez is megfelelően legyen kezelve, “klónozva”, másolva.

Ha eddig nem vált világossá, akkor tegyük tisztába: kétféle állapotunk van ebben a modellben. Az egyik az aktuális kalkuláció állapota. Ez elvileg végtelen, illetve csak azért véges, mert a gépek mérete véges. A másik a hívási sorrend állapota. Ez véges. Ha az API-t egy véges automatával modellezzük, és minden egyes metódushívást egy átmenet, és minden egyes interface, ami a visszatérési típus akkor ez a modell stimmel is. Amikor bevezettük (volna) az extra boolean változót, akkor a kalkuláció állapotában tartottuk volna nyilván a hívási sorrend állapotát. Ez nem jó, ez nem az amit a tigrisek szeretnek.

Eddig interfészeket használtunk az állapotok reprezentálására, és nagyon sokat segít abban, hogy az IDE csak azokat a metódusokat kínálja fel kódkiegészítésnél, amik az adott hívási sorrendben hívhatók. Használhatnánk osztályokat is. Miért nem szoktuk? Azért, mert sokat kell gépelni. Az interfészben csak fel kell sorolni a metódusokat, az osztályokat implementálni kell: legalábbis minden egyes metódusnak meg kell hívni a központi (példánkban StringTool) megfelelő metódusát.

Mi lenne az előny? Az első, hogy lehetőségünk lesz arra, hogy egy metódus visszatérési típusa más legyen, attól függően, hogy milyen állapotból hívtuk. Amíg csak interfészeket használtunk az állapotok reprezentálására, addig a unJavaLangized minden esetben a DashDotConverted állapotba vitte át az automatát. Ez itt nem gond, de általánosságban, egy összetettebb API esetében nagy előny, ha lehetőségünk van egy metódust több, különböző állapot között használni.

A másik nagy előny, hogy a különböző állapotokat reprezentáló osztályokban az egyes metódus átmenetek különbözőképpen lehetnek implementálva. Konkrétan, a mi esetünkben: az egyik osztályban a unJavaLangized() az eredeti, azonos nevű metódust hívja meg, míg a másikban meghívhatjuk a dashUnJavaLangized metódust. Nem kell bevezetnünk új változót, csak a hívási sorrend mögötti logika lesz összetettebb. De ez nem baj, amíg karbantartható, és amíg nem az a fő feladatunk, hogy template kódot kell írnunk az időnk 90%-ban. Pedig erre ezzel a megközelítéssel lenne esély, de a programozó legnagyobb erénye a lustaság: inkább ír programot, ami kódot ír, mintsem, hogy kódot írjon. Más szavakkal: programozni szeretünk, nem kódolni.

Kulcsszavak a továbbolvasáshoz, és a fluent API-t támogató annotációs processzorhoz: GitHub, verhas, fluflu.

Ezen kívül érdemes elolvasni Lukas blogbejegyzését is.

Ninja implementálás, level interfész

Azt a felelőtlen ígéretet tettem az Interfész implementálás, level ninja című posztban, hogy hétvégén megírom, hogy pontosan mi is a megoldás, meg, hogy honnan jött össze ez az egész furcsa kérdés. Emlékeztetőnek, aki lusta visszalapozni, az volt a kérdés, hogy …

Hogyan fordulhat olyan elő, hogy egy osztályunk implementálni akar két interface-t, a két interface-ben nincsenek egymást “ütő” metódusok, a Java compiler mégsem engedi meg, hogy mind a két interface-t implementáljuk.

Hogy még egyszerűbb legyen a dolog: I1 és I2 interface-k egyike sem definiál egyetlen metódust sem, sem direk módon, sem pedig öröklődésen keresztül. A C osztály pedig csak ennyi:

public class C implements I1, I2{

}

És mégsem fordul le. Mi lehet?

Mielőtt belekezdenénk, néhány megjegyzés:

  • Az, hogy az interfészek egyike sem implementál metódusokat itt azt jelenti, hogy nem csak ők maguk nem implementálnak metódust, de egyetlen olyan interfész sem, amelyet bármelyik a példában szereplő interfész kiterjeszt, akár explicit, akár implicit módon.
  • Van olyan eset is, amikor két interfész azért nem implementálható egy osztályban, mert egymással nem kompatibilis metódusokat definiálnak. Ez azt jelenti, hogy az egyik interfészben van olyan szignatúrájú metódus, amelyik szignatúrával található metódus a másikban is, de a két metódus visszatérési típusa egymással nem kompatibilis. Nem kompatibilis, azaz nem létezik olyan osztály, amelyik mind akét típusnak leszármazottja lenne, és ezért nem lehet mind a két metódust egy metódusban implementálni az implemetáló osztályban. Mint McAllister megjegyezte:

Ha egy interfacenek van egy metódusa és annak egy visszatérési értéke, akkor az implementáló osztály amikor megvalósítja az interface metódusát használhat egy leszármazott típust a megvalósítandó metódus visszatérési értékének. Amúgy nem megy.

pl.

interface I3 { Object a();}
class B implements I3 { String a(); }

Daniel McAllister

Ez nagyon így van. Szóval lehet, hogy egy kicsit pongyolán fogalmaztam, de őszintén: a mostani megfogalmazás sem precíz, és nem is akarom, mert akkor inkább idemásolhatnám a java biblia megfelelő idézetét, például a 9.4.1.1 fejezetet, és együtt énekelhetnénk a hozzá illő zsoltárt. Ugye a Java programozó bibliája a jsl.

Szóval: hogyan kell olyan interfészeket gyártani, amiket ha implementálni akarok egy osztályban, akkor a fordító nem csak kiröhög, hogy próbálkozzam csak, úgysem fog menni, hanem egyenesen megtagadja még a lehetőséget is. Kofa elég gyorsan kitalálta (laza a projekt?), hogy ez csak úgy lehet, ha van egy közös generikus ősük, amit más konkrét típussal terjesztenek ki. Nah ez így eléggé kódolt, de ha kód, akkor legyen, így már érthetőbb lesz:

public interface S<T> {}
public interface I1 extends S<String> {}
public interface I2 extends S<Number> {}

Ez Kofa kommentjéből van. Miért nem lehet implementálni egyszerre I1 és I2 interfészeket?Hát azért mert …. (wait for it)… drámai dobpergés…

Előbb elmondom, hogy hogyan akadtam ebbe bele. Mert élő ember, legalábbis olyan aki nem hatoldalú szabályos test, nincs aki addig olvassa a szabványt, amíg meg nem világosodik neki, hogy ilyent nem lehet. A dolog egy kellemes(nek nem mondható, esős és hideg) nyári délelőtt jött elő, amikor éppen GWT GUI-t programozgattam a fűtött irodában (június, 2013). Mindenféle eseményeket kellett kezelni, Event-eket, ha így ismerősebb. Eseményeket lehetett dobni (még mindig jobb, mint kivételes törpéket), és amikor egy esemény sorra került az eseménybuszon, akkor meghívta az esemény kezelőjét. Ez esemény kezelője implementálta a kezelő interfész, amiben definiálva van egy metódus, aminek a neve általában onEseménytípus. Valahogy így:

interface HappyHandler extends EventHandler {
  public void onHappiness(HappyEvent event);
}

interface HasHappyEvents {
  public HandlerRegistration addHappyHandler(HappyHandler handler);
}
class HappyEvent extends AbstractEvent{
  public static AbstractEvent.Key KEY = new AbstractEvent.Key(){...}

  public GwtEvent.Key getKey(){
    return KEY; 
  }
  ...
}
class HappyEvent extends GwtEvent {
  static Key<HappyEvent,HappyHandler> KEY = new Key<HappyEvent,HappyHandler>(){
    protected void fire(HappyHandler handler, HappyEvent event) {
       handler.onHappiness(event);
    };
   ...
}

Nem vagyunk mi itten túl boldogok? Hányszor írtuk már le azt, hogy ‘happy’? Mi van itt, születésnap?

Na szóval: legyen az esemény neve akármi, amit akarunk, a boldogságot nem lehet megúszni, essünk túl rajta: HappyEvent. Egyszer csak le kell írnunk ezt a szót a kódban, de az ideális az lenne, ha nem kellene többet. A kezelő interfész legyen Handler. Eddig HappyEventHandler volt, de ha ez az interfész a HappyEvent osztály publikus nested interfésze, akkor ezentúl a neve Handler, ha meg kívülről kell valakinek (és persze fog neki), akkor HappyEvent.Handler. Ez kell implementálnia minden olyan osztálynak, amelyik ezt az eseményt kezelni szeretné. (Az ugye világos, hogy nested interface és nem inner interfész! Vigyázz, beugratós!)

Az sem szerencsés, hogy az eseményt kezelő metódus public void onHappiness(HappyEvent event);. Itt ugyan egy kicsit el van fedve a dolog, de igazából, ha konzekvensek vagyunk, akkor ez így nézne ki: public void onHappyEvent(HappyEvent event); Már megint túl sok a boldogság. Miért nem lehet egyszerűen public void on(HappyEvent event);. Aztán ha több eseménykezelőt implementálunk, akkor majd az argumentum megmondja, hogy melyikről van éppen szó. Ezért jó a túltöltés (overload). Ha pedig megállapodtunk abban, hogy minden eseménykezelő metódus neve on, akkor miért kell ezt minden Handler interfészben leírni? Miért nem lehet egy AbstractRidiculousSimpleSuperHandler<MyEvent> interfész, amit kiterjesztünk, megadva MyEvent-ként azt az eseményt, amelyiknek amúgy is a nested interfészét definiáljuk éppen.

Megérkeztünk? Ott vagyunk már? (Szamár!!!!)

Bizony meg, mert abban a pillanatban, amikor a refaktorálással idáig eljutottam, és implementálni akartam egy osztályban két eseményt a javac pofán vágott, hogy csak néztem, mint magas kilátó a google keresőre. Aztán rákerestem, gondolkodtam, és lassanként megvilágosodott, hogy miért is. (Dotnetesnek meg ne mutassad a cikket, mert itt fog ugrálni körülötted, hogy bezzeg a C# … És igaza van!)

Mert mi történik akkor ha van egy eseményem, amelyiket kezelni akarok? Az eseményhez be van jegyezve egy (vagy több) esemény kezelő objektum. Ebben kell meghívni az eseménykezelő interfész által definiált on metódust. De mi van akkor, ha a kezelő egy olyan metódust implementál, amelyik egy generikus szuper interfészből jön? Nézzük meg az egész példát, az egyszerűség kedvéért, ha valaki ki akarja copy/paste egy osztályba van minden belezsúfolva:

package genericSample;

public class DemoClass {
  interface AbstractRidiculousSimpleSuperHandler<MyEvent> {
    void on(MyEvent event);
  }

  static class Karacsony {
    interface Handler extends AbstractRidiculousSimpleSuperHandler<Karacsony> {
    }
  }

  static class Szulinap {
    // interface Handler extends AbstractRidiculousSimpleSuperHandler<Szulinap>
    // {
    interface Handler {
      void on(Szulinap event);
    }
  }

  static class AjandekotKapok implements Szulinap.Handler, Karacsony.Handler {

    @Override
    public void on(Karacsony event) {
      System.out.println("Karacsony van");
    }

    @Override
    public void on(Szulinap event) {
      System.out.println("Szulinap van");
    }

  }

  public static void main(String[] a) {
    Karacsony karacsony = new Karacsony();
    Szulinap szulinap = new Szulinap();
    AjandekotKapok kezelo = new AjandekotKapok();
    AbstractRidiculousSimpleSuperHandler kk = kezelo;
    kk.on(karacsony);
    kezelo.on(szulinap);
  }
}

Ha lehetne, hogy a szülinap is kiterjessze ugyanazt az interfészt (ahogy a kikommentezett részben van), akkor az on() metódus meghívásakor nem tudná a Java, hogy melyiket is kell meghívnia. Ugyanis kettő van, de ugyanarra az egy, a AbstractRidiculousSimpleSuperHandler interfészben definiált metódusra hivatkoznának. Amikor túllovaglás (override) van, akkor a futtató rendszer megnézi, hogy milyen osztályhoz tartozik az aktuális objektum. Az amelyikre a fenti példában a kezelo és a kk változó is hivatkozik. Tulajdonképpen a kk változó teljesen felesleges is, hiszen nem az az érdekes, hogy a változónak milyen a típusa, hanem az, hogy az objektumnak milyen a típusa: futási időben dől el, hogy melyik metódust fogja meghívni a JVM, és mindegy hogy az objektumra hivatkozó referenciát milyen típusú változóban tároltuk. Az objektum típusa pedig AjandekotKapok, ami lehet Szulinap.Handler, Karacsony.Handler vagy AbstractRidiculousSimpleSuperHandler. De, hogy éppen milyen generikus argumentummal, az bizony nincs benne az objektumban. Így egy on metódusból nem is lehet kettő az implementációban.

Mi akkor a megoldás? El kell dobni a közös generikus őst, és körmölni kell rogyásig, minden egyes interfészben leírva, hogy on(EseményTipus esemeny);? Gyenge nyelv lenne a Java, ha ez lenne a megoldás. Ehelyett az egyes kezelőket inner classokba (és nem nested) kell elhelyezni, és mindegyik csak egy kezelő interfészt implementál:

package genericSample;

public class DemoClass {
  interface AbstractRidiculousSimpleSuperHandler<MyEvent> {
    void on(MyEvent event);
  }

  static class Karacsony {
    interface Handler extends AbstractRidiculousSimpleSuperHandler<Karacsony> {
    }
  }

  static class Szulinap {
    interface Handler extends AbstractRidiculousSimpleSuperHandler<Szulinap> {
      void on(Szulinap event);
    }
  }

  static class AjandekotKapok {

    class K implements Karacsony.Handler {
      @Override
      public void on(Karacsony event) {
        System.out.println("Karacsony van");
      }
    }

    class S implements Szulinap.Handler {
      @Override
      public void on(Szulinap event) {
        System.out.println("Szulinap van");
      }
    }

    public final K k = new K();
    public final S s = new S();

  }

  public static void main(String[] a) {
    Karacsony karacsony = new Karacsony();
    Szulinap szulinap = new Szulinap();
    AjandekotKapok kezelo = new AjandekotKapok();
    kezelo.k.on(karacsony);
    kezelo.s.on(szulinap);
  }
}

Ezzel ugyan nehézkesebb a meghívás, hiszen nem elég azt leírni, hogy kezelo, hanem utána kell írni azt is, hogy melyik kezelő, pedig ezt lehetne tudni az esemény típusából is, viszont a jó hír, hogy ezt valójában, a konkrét példában nem kell. Az esemény kezelőket ugyanis nem a mi kódunk hívja meg, hanem a kertrendszer, így a k és s változókra sincs szükség. Az is előnye ennek a megoldásnak, hogy amíg a külső osztály maga volt az eseménykezelő, addig saját magát regisztrálta be a konstruktorból, paraméterként átadva a this értékét. Ez pedig az álmoskönyv szerint elég egészségtelen. Így viszont a frissen és ropogósan létrehozott inner class-okat regisztrálják be, így ezt az antipatternt is elkerüljük.