Bevezetés az AutoValue-ba

1. Áttekintés

Az AutoValue egy forráskód-generátor a Java számára, pontosabban egy könyvtár számára forráskód generálása értékobjektumokhoz vagy értéktípusú objektumokhoz.

Egy érték típusú objektum létrehozásához mindössze annyit kell tennie, hogy kommentálja egy absztrakt osztályt a @AutoValue annotáció és állítsd össze az osztályodat. Ami generálódik, egy érték objektum hozzáférési módszerekkel, paraméterezett konstruktorral, felülírva toString (), egyenlő (Object) és hash kód() mód.

A következő kódrészlet a egy gyors példa egy absztrakt osztály, amely fordításkor egy elnevezett értékobjektumot eredményez AutoValue_Person.

@AutoValue absztrakt osztály Személy {statikus Személy létrehozása (String név, int kor) {return new AutoValue_Person (név, életkor); } absztrakt Karakterlánc neve (); absztrakt int kor (); } 

Folytassuk, és többet megtudhatunk az értékobjektumokról, miért van szükségünk rájuk, és az AutoValue hogyan segíthet abban, hogy a kód létrehozása és újrafeldolgozása sokkal kevésbé időigényes legyen.

2. Maven Setup

Az AutoValue Maven projektekben való használatához a következő függőséget kell tartalmaznia a pom.xml:

 com.google.auto.value automatikus érték 1.2 

A legfrissebb verzió ezen a linken található.

3. Érték-típusú objektumok

Az értéktípusok a könyvtár végtermékei, ezért, hogy értékeljük a helyét a fejlesztési feladatainkban, alaposan meg kell értenünk az értéktípusokat, melyek azok, mi nem, és miért van szükségünk rájuk.

3.1. Mik azok az értéktípusok?

Az értéktípusú objektumok olyan objektumok, amelyek egymás közötti egyenlőségét nem az identitás, hanem a belső állapotuk határozza meg. Ez azt jelenti, hogy egy értéktípusú objektum két példánya egyenlőnek tekinthető, amennyiben azonos mezőértékekkel rendelkezik.

Az értéktípusok általában változhatatlanok. A mezőiket meg kell csinálni végső és nem szabad szetter módszereket, mivel ez a példányosítás után változtathatóvá teszi őket.

Az összes mezőértéket konstruktorral vagy gyári módszerrel kell elfogyasztaniuk.

Az értéktípusok nem JavaBeans-ok, mivel nem rendelkeznek alapértelmezett vagy nulla argumentum-készítővel, és nincsenek beállító módszereik sem, hasonlóan, ezek nem adatátviteli objektumok, sem pedig sima Old Java objektumok.

Ezenkívül egy értéktípusú osztálynak véglegesnek kell lennie, hogy ne legyenek kibővíthetők, legalábbis az, hogy valaki felülírja a módszereket. A JavaBeans-nak, a DTO-knak és a POJO-knak nem kell véglegesnek lenniük.

3.2. Értéktípus létrehozása

Feltételezve, hogy létre akarunk hozni egy úgynevezett érték-típust Foo nevű mezőkkel szöveg és szám. Hogyan folytatnánk?

Végleges osztályt készítünk, és minden mezőjét véglegesnek jelöljük. Ezután az IDE segítségével generálnánk a konstruktort, a hash kód() módszer, az egyenlő (Object) módszer, az getterek mint kötelező módszerek és a toString () módszer, és lenne egy ilyen osztályunk:

nyilvános döntő osztály Foo {privát végleges String szöveg; privát végső int szám; public Foo (Karaktersorozat, int szám) {this.text = text; ez.szám = szám; } // standard getters @Override public int hashCode () {return Objects.hash (szöveg, szám); } @Orride public String toString () {return "Foo [text =" + text + ", number =" + number + "]"; } @Orride public boolean equals (Object obj) {if (this == obj) return true; if (obj == null) false értéket ad vissza; if (getClass ()! = obj.getClass ()) false értéket ad vissza; Foo egyéb = (Foo) obj; ha (szám! = egyéb.szám) hamis értéket ad vissza; if (text == null) {if (other.text! = null) false-t ad vissza; } else if (! text.equals (other.text)) {return false; } return true; }}

Miután létrehozta a Foo, azt várjuk, hogy a belső állapota az egész életciklusa alatt változatlan marad.

Amint a következő alfejezetben látni fogjuk a hash kód egy objektumnak példányról példányra kell változnia, de értéktípusokhoz azokat a mezőket kell összekapcsolnunk, amelyek meghatározzák az értékobjektum belső állapotát.

Ezért akár ugyanazon objektum mezőjének megváltoztatása megváltoztatná a hash kód érték.

3.3. Hogyan működnek az értéktípusok

Az értéktípusoknak változhatatlannak kell lenniük, hogy megakadályozzák az alkalmazás belső állapotának bármilyen változását azután, hogy azokat példányosították.

Amikor össze akarunk hasonlítani bármely két értéktípusú objektumot, ezért használnunk kell a egyenlő (Object) módszere Tárgy osztály.

Ez azt jelenti, hogy mindig felül kell írnunk ezt a módszert a saját értéktípusainkban, és csak akkor kell igazat adnunk, ha az általunk összehasonlított értékobjektumok mezőinek értéke azonos.

Sőt, hogy értékobjektumainkat hash-alapú gyűjteményekben használjuk, mint például HashSets és HashMaps törés nélkül, megfelelően végre kell hajtanunk a hash kód() módszer.

3.4. Miért van szükségünk értéktípusokra?

Az értéktípusok iránti igény elég gyakran felmerül. Ezek olyan esetek, amikor felül akarjuk írni az eredeti alapértelmezett viselkedését Tárgy osztály.

Mint már tudjuk, a Tárgy osztály két objektumot tekint egyenlőnek, ha azonos az identitásuk céljaink szempontjából két objektumot egyenlőnek tekintünk, ha ugyanaz a belső állapotuk.

Feltéve, hogy egy pénzobjektumot szeretnénk létrehozni az alábbiak szerint:

nyilvános osztály MutableMoney {magán hosszú összeg; privát String pénznem; public MutableMoney (hosszú összeg, String pénznem) {this.amount = összeg; this.valuta = valuta; } // szokásos mérőeszközök és beállítók}

A következő tesztet futtathatjuk rajta az egyenlőség teszteléséhez:

@Test public void givenTwoSameValueMoneyObjects_whenEqualityTestFails_thenCorrect () {MutableMoney m1 = új MutableMoney (10000, "USD"); MutableMoney m2 = új MutableMoney (10000, "USD"); assertFalse (m1.egyenlő (m2)); }

Figyelje meg a teszt szemantikáját.

Úgy tekintjük, hogy akkor telt el, amikor a két pénz objektum nem egyenlő. Ez azért van, mert nem írtuk felül a egyenlő módszer tehát az egyenlőséget az objektumok memória-referenciáinak összehasonlításával mérjük, amelyek természetesen nem lesznek különbözőek, mert különböző tárgyak foglalják el a különböző memóriahelyeket.

Minden objektum értéke 10 000 USD, de Java azt mondja nekünk, hogy a pénzobjektumaink nem egyenlőek. Azt akarjuk, hogy a két objektum csak akkor teszteljen egyenlőtlenségeket, ha vagy a pénznemek különböznek, vagy a pénznem típusok eltérőek.

Most hozzunk létre egy egyenértékű objektumot, és ezúttal hagyjuk, hogy az IDE generálja a kód nagy részét:

nyilvános döntő osztály ImmutableMoney {privát végső hosszú összeg; privát végleges String pénznem; public ImmutableMoney (hosszú összeg, String pénznem) {this.amount = összeg; this.valuta = valuta; } @Orride public int hashCode () {final int prime = 31; int eredmény = 1; eredmény = prím * eredmény + (int) (összeg ^ (összeg >>> 32)); eredmény = elsődleges * eredmény + ((valuta == null)? 0: valuta.hashCode ()); visszatérési eredmény; } @Orride public boolean equals (Object obj) {if (this == obj) return true; if (obj == null) false értéket ad vissza; if (getClass ()! = obj.getClass ()) false értéket ad vissza; ImmutableMoney other = (ImmutableMoney) obj; ha (összeg! = egyéb.összeg) hamis értéket ad vissza; if (valuta == null) {if (más.valuta! = null) hamis értéket ad vissza; } else if (! currency.equals (other.currency)) false értéket ad vissza; return true; }}

Az egyetlen különbség az, hogy felülírtuk a egyenlő (Object) és hash kód() módszerekkel, most már ellenőrizhetjük, hogyan akarjuk a Java összehasonlítani a pénzobjektumainkat. Futtassuk az egyenértékű tesztet:

@Test public void givenTwoSameValueMoneyValueObjects_whenEqualityTestPasses_thenCorrect () {ImmutableMoney m1 = new ImmutableMoney (10000, "USD"); ImmutableMoney m2 = új ImmutableMoney (10000, "USD"); assertTrue (m1.egyenlő (m2)); }

Figyelje meg ennek a tesztnek a szemantikáját, azt várjuk, hogy akkor fog teljesülni, amikor mindkét pénz objektum egyenlő tesztet végez a egyenlő módszer.

4. Miért pont az AutoValue?

Most, hogy alaposan megértettük az értéktípusokat és azt, hogy miért van szükségünk rájuk, megnézhetjük az AutoValue értékét és azt, hogy ez hogyan kerül az egyenletbe.

4.1. A kézi kódolás kérdései

Amikor olyan értéktípusokat hozunk létre, mint az előző szakaszban, számos kérdéssel fogunk találkozni rossz tervezés és sok kazán kód.

Egy két mező osztályban 9 sor kód lesz: egy a csomag deklarálásához, kettő az osztály aláírásához és záró zárójeléhez, kettő a mező deklarációihoz, kettő a konstruktorokhoz és a záró zárójeléhez és kettő a mezők inicializálásához, de akkor szükségünk van a mezőkhöz mindegyik további három kódsort vesz fel, így hat extra sort készít.

A hash kód() és equTo (Objektum) metódusok körülbelül 9, illetve 18 sort igényelnek, és felülírják a toString () módszer újabb öt sort ad hozzá.

Ez azt jelenti, hogy a két mezőosztályunkhoz jól formázott kódbázisra lenne szükség körülbelül 50 sornyi kód.

4.2 IDE-k a mentéshez?

Ez könnyű egy olyan IDE-vel, mint az Eclipse vagy az IntilliJ, és csak egy vagy két értéktípusú osztály hozható létre. Gondoljon ilyen osztályok létrehozására, vajon ugyanolyan egyszerű lenne-e még akkor is, ha az IDE segít nekünk?

Gyors előre, néhány hónappal az úton. Tegyük fel, hogy újra meg kell vizsgálnunk a kódexünket és módosítanunk kell a szabályzatunkat Pénz osztályokat, és talán átalakítja a valuta mező a Húr type egy másik értéktípusnak hívják Valuta.

4.3 Az IDE-k nem igazán olyan hasznosak

Az Eclipse-hez hasonló IDE nem egyszerűen szerkesztheti számunkra a hozzáférési módszereinket, sem a toString (), hash kód() vagy egyenlő (Object) mód.

Ezt az átalakítást kézzel kell elvégezni. A kód szerkesztése növeli a hibák lehetőségét, és minden új mezővel, amelyet hozzáadunk a Pénz osztályban a sorok száma ugrásszerűen növekszik.

Felismerve azt a tényt, hogy ez a forgatókönyv előfordul, hogy gyakran és nagy mennyiségben fordul elő, igazán értékelni fogjuk az AutoValue szerepét.

5. Példa automatikus értékre

Az AutoValue megoldja a problémát, hogy kiveszi az utunkból az összes kazánlap kódot, amelyről az előző szakaszban beszéltünk, hogy soha ne kelljen megírnunk, szerkesztenünk vagy akár el is olvassuk.

Ugyanezt fogjuk vizsgálni Pénz példa, de ezúttal az AutoValue értékkel. Ezt az osztályt hívjuk AutoValueMoney a következetesség érdekében:

@AutoValue public abstract class AutoValueMoney {public abstract String getCurrency (); public abstract hosszú getAmount (); public static AutoValueMoney create (String pénznem, hosszú összeg) {return new AutoValue_AutoValueMoney (valuta, összeg); }}

Az történt, hogy írunk egy absztrakt osztályt, absztrakt hozzáférőket definiálunk hozzá, de mezőket nem, az osztályokkal feljegyezzük @AutoValue - összesen csak 8 kódsor, és - javac generál számunkra egy konkrét alosztályt, amely így néz ki:

public final class AutoValue_AutoValueMoney kiterjeszti az AutoValueMoney {private final String valutát; magán végső hosszú összeg; AutoValue_AutoValueMoney (Karakterlánc pénzneme, hosszú összeg) {if (valuta == null) dobja új NullPointerException (valuta); this.valuta = valuta; ez.összeg = összeg; } // standard getters @Orride public int hashCode () {int h = 1; h * = 1000003; h ^ = valuta.hashCode (); h * = 1000003; h ^ = összeg; visszatér h; } @Orride public boolean egyenlő (Object o) {if (o == this) {return true; } if (o AutoValueMoney példánya) {AutoValueMoney that = (AutoValueMoney) o; visszatérés (this.currency.equals (that.getCurrency ())) && (this.amount == that.getAmount ()); } return false; }}

Soha nem kell közvetlenül foglalkoznunk ezzel az osztállyal, és akkor sem kell szerkesztenünk, ha további mezőket kell hozzáadnunk, vagy módosítanunk kell a mezőinket, például valuta forgatókönyv az előző szakaszban.

Javac mindig megújítja a számunkra a frissített kódot.

Az új értéktípus használata közben az összes hívó csak a szülőtípust látja, amint azt a következő egységteszteken láthatjuk.

Itt van egy teszt, amely ellenőrzi, hogy a mezőink helyesen vannak-e beállítva:

@Test public void givenValueTypeWithAutoValue_whenFieldsCorrectlySet_thenCorrect () {AutoValueMoney m = AutoValueMoney.create ("USD", 10000); assertEquals (m.getAmount (), 10000); assertEquals (m.getCurrency (), "USD"); }

Egy teszt annak ellenőrzésére AutoValueMoney az azonos pénznemű és azonos összegű teszt objektumok egyenlőek:

@Test public void given2EqualValueTypesWithAutoValue_whenEqual_thenCorrect () {AutoValueMoney m1 = AutoValueMoney.create ("USD", 5000); AutoValueMoney m2 = AutoValueMoney.create ("USD", 5000); assertTrue (m1.egyenlő (m2)); }

Amikor egy pénzobjektum pénznem típusát GBP-re változtatjuk, a teszt: 5000 GBP == 5000 USD már nem igaz:

@Test public void given2DifferValueTypesWithAutoValue_whenNotEqual_thenCorrect () {AutoValueMoney m1 = AutoValueMoney.create ("GBP", 5000); AutoValueMoney m2 = AutoValueMoney.create ("USD", 5000); assertFalse (m1.egyenlő (m2)); }

6. Autóérték építőkkel

Az általunk megnézett első példa az AutoValue alapvető használatát fedi fel, statikus gyári módszerrel, nyilvános létrehozási API-ként.

Vegyük észre, hogy ha minden mezőnk lenne Húrok, könnyű lenne kicserélni őket, amint átadjuk őket a statikus gyári módszernek, például a összeg helyén valuta és fordítva.

Ez különösen akkor fordulhat elő, ha sok mezőnk van, és mindegyikünk Húr típus. Ezt a problémát súlyosbítja az a tény, hogy az AutoValue az összes mező inicializálása a konstruktoron keresztül történik.

A probléma megoldásához a építész minta. Szerencsére. ezt az AutoValue generálhatja.

Az AutoValue osztályunk valójában nem sokat változik, kivéve, hogy a statikus gyári módszert építő váltja fel:

@AutoValue public abstract class AutoValueMoneyWithBuilder {public abstract String getCurrency (); public abstract hosszú getAmount (); static Builder builder () {return new AutoValue_AutoValueMoneyWithBuilder.Builder (); } @ AutoValue.Builder absztrakt statikus osztály Builder {absztrakt Builder setCurrency (String pénznem); absztrakt Builder setAmount (hosszú összeg); absztrakt AutoValueMoneyWithBuilder build (); }}

A generált osztály pontosan ugyanaz, mint az első, de egy konkrét belső osztály jön létre az építtető számára, valamint az absztrakt módszerek megvalósítása az építőben:

a statikus végső osztályú Builder kiterjeszti az AutoValueMoneyWithBuilder.Builder {private String valuta; magán hosszú összeg; Builder () {} Builder (AutoValueMoneyWithBuilder forrás) {this.currency = source.getCurrency (); this.amount = forrás.getAmount (); } @Orride public AutoValueMoneyWithBuilder.Builder setCurrency (String currency) {this.currency = currency; adja vissza ezt; } @Orride public AutoValueMoneyWithBuilder.Builder setAmount (hosszú összeg) {this.amount = összeg; adja vissza ezt; } @Orride public AutoValueMoneyWithBuilder build () {String missing = ""; if (valuta == null) {hiányzik + = "valuta"; } if (összeg == 0) {hiányzik + = "összeg"; } if (! missing.isEmpty ()) {dobjon új IllegalStateException-t ("Hiányzó kötelező tulajdonságok:" + hiányzik); } return new AutoValue_AutoValueMoneyWithBuilder (this.valuta, this.amount); }}

Vegye figyelembe azt is, hogy a teszt eredményei hogyan változnak.

Ha tudni akarjuk, hogy a mezőértékek az építőn keresztül valóban helyesen vannak-e beállítva, akkor elvégezhetjük ezt a tesztet:

@Test public void givenValueTypeWithBuilder_whenFieldsCorrectlySet_thenCorrect () {AutoValueMoneyWithBuilder m = AutoValueMoneyWithBuilder.builder (). setAmount (5000) .setCurrency ("USD"). build (); assertEquals (m.getAmount (), 5000); assertEquals (m.getCurrency (), "USD"); }

Annak tesztelése, hogy az egyenlőség a belső állapottól függ:

@Test public void given2EqualValueTypesWithBuilder_whenEqual_thenCorrect () {AutoValueMoneyWithBuilder m1 = AutoValueMoneyWithBuilder.builder () .setAmount (5000) .setCurrency ("USD"). Build (); AutoValueMoneyWithBuilder m2 = AutoValueMoneyWithBuilder.builder () .setAmount (5000) .setCurrency ("USD"). Build (); assertTrue (m1.egyenlő (m2)); }

És amikor a mezőértékek eltérnek:

@Test public void given2DifferValueTypesBuilder_whenNotEqual_thenCorrect () {AutoValueMoneyWithBuilder m1 = AutoValueMoneyWithBuilder.builder () .setAmount (5000) .setCurrency ("USD"). Build (); AutoValueMoneyWithBuilder m2 = AutoValueMoneyWithBuilder.builder () .setAmount (5000) .setCurrency ("GBP"). Build (); assertFalse (m1.egyenlő (m2)); }

7. Következtetés

Ebben az oktatóanyagban bemutattuk a Google AutoValue könyvtárának alapjait, és azt, hogyan lehet értéktípusokat létrehozni részünkről nagyon kevés kóddal.

A Google AutoValue alternatívája a Lombok projekt - itt megtekintheti a Lombok használatáról szóló bevezető cikket.

Ezen példák és kódrészletek teljes megvalósítása megtalálható az AutoValue GitHub projektben.