Úvod do funkcie AutoValue

1. Prehľad

AutoValue je generátor zdrojového kódu pre Javu, presnejšie je to knižnica pre generovanie zdrojového kódu pre hodnotové objekty alebo objekty s typovou hodnotou.

Ak chcete vygenerovať objekt typu hodnoty, musíte iba urobiť anotovať abstraktnú triedu pomocou @AutoValue anotácia a zostavte svoju triedu. Generuje sa hodnotový objekt s prístupovými metódami, parametrizovaným konštruktorom, ktorý je správne prepísaný toString (), sa rovná (Object) a hashCode () metódy.

Nasledujúci úryvok kódu je rýchly príklad abstraktnej triedy, ktorej výsledkom bude pri zostavení hodnotový objekt s názvom AutoValue_Person.

@AutoValue abstraktná trieda Osoba {statická Osoba vytvoriť (meno reťazca, int vek) {vrátiť nový AutoValue_Person (meno, vek); } abstrakt Názov reťazca (); abstraktný vek (); } 

Poďme pokračovať a dozvieme sa viac o hodnotových objektoch, o tom, prečo ich potrebujeme a o tom, ako môže funkcia AutoValue pomôcť znížiť časovú náročnosť úlohy generovania a refaktoringu kódu.

2. Nastavenie Maven

Ak chcete používať AutoValue v projektoch Maven, musíte do súboru zahrnúť nasledujúcu závislosť pom.xml:

 automatická hodnota com.google.auto.value 1.2 

Najnovšiu verziu nájdete po kliknutí na tento odkaz.

3. Predmety s hodnotovou hodnotou

Typy hodnôt sú konečným produktom knižnice, takže aby sme ocenili jej miesto v našich vývojových úlohách, musíme dôkladne porozumieť typom hodnôt, čo sú to, čo nie sú a prečo ich potrebujeme.

3.1. Čo sú typy hodnôt?

Objekty typu hodnoty sú objekty, ktorých vzájomná rovnosť nie je určená identitou, ale skôr ich vnútorným stavom. To znamená, že dve inštancie objektu s typovou hodnotou sa považujú za rovnaké, pokiaľ majú rovnaké hodnoty poľa.

Typy hodnôt sú zvyčajne nemenné. Ich polia musia byť urobené konečné a nesmú mať stavač metódy ich umožnia zmeniť ich po vytvorení inštancie.

Všetky hodnoty poľa musia spotrebovať pomocou konštruktora alebo továrenskou metódou.

Typy hodnôt nie sú JavaBeans, pretože nemajú predvolený alebo nulový konštruktor argumentov a rovnako nemajú ani metódy setter, nie sú to objekty na prenos údajov ani obyčajné staré objekty Java.

Trieda s hodnotami musí byť navyše konečná, aby ju nebolo možné rozšíriť, minimálne aby niekto prepísal metódy. JavaBeans, DTO a POJO nemusia byť konečné.

3.2. Vytvorenie typu hodnoty

Za predpokladu, že chceme vytvoriť typ hodnoty s názvom Foo s poliami tzv text a číslo. Ako by sme to riešili?

Vytvorili by sme záverečnú triedu a všetky jej polia by sme označili ako konečné. Potom by sme pomocou IDE vygenerovali konštruktor, hashCode () metóda, rovná sa (Objekt) metóda, getry ako povinné metódy a a natiahnuť() metóda a mali by sme triedu ako takúto:

public final class Foo {private final String text; súkromné ​​konečné číslo int; public Foo (text reťazca, číslo int) {this.text = text; this.number = number; } // standardne getre @Override public int hashCode () {return Objects.hash (text, number); } @Override public String toString () {return "Foo [text =" + text + ", number =" + number + "]"; } @Override public boolean equals (Object obj) {if (this == obj) return true; if (obj == null) return false; if (getClass ()! = obj.getClass ()) return false; Foo other = (Foo) obj; if (number! = other.number) return false; if (text == null) {if (other.text! = null) return false; } else if (! text.equals (other.text)) {return false; } návrat pravdivý; }}

Po vytvorení inštancie Foo, očakávame, že jeho vnútorný stav zostane rovnaký počas celého jeho životného cyklu.

Ako uvidíme v nasledujúcej podsekcii the hashCode objektu sa musí meniť z inštancie na inštanciu, ale pre typy hodnôt ho musíme viazať na polia, ktoré definujú vnútorný stav hodnotového objektu.

Preto by aj zmena poľa rovnakého objektu zmenila hashCode hodnotu.

3.3. Ako fungujú typy hodnôt

Dôvodom, prečo musia byť typy hodnôt nemenné, je zabrániť akejkoľvek zmene ich vnútorného stavu aplikáciou po ich vytvorení inštancie.

Kedykoľvek chceme porovnávať akékoľvek dva objekty s typovou hodnotou, musíme preto použiť rovná sa (Objekt) metóda Objekt trieda.

To znamená, že túto metódu musíme vždy prepísať v našich vlastných hodnotových typoch a true vrátiť, iba ak majú polia porovnávaných hodnotových objektov rovnaké hodnoty.

Navyše, aby sme mohli používať svoje hodnotové objekty v zbierkach založených na hashe, ako sú HashSets a HashMaps bez zlomenia, musíme správne implementovať hashCode () metóda.

3.4. Prečo potrebujeme typy hodnoty

Potreba typov hodnôt sa objavuje pomerne často. Ide o prípady, kedy by sme chceli potlačiť predvolené správanie originálu Objekt trieda.

Ako už vieme, predvolená implementácia Objekt trieda považuje dva objekty za rovnocenné, ak majú rovnakú identitu pre naše účely považujeme dva objekty za rovnocenné, keď majú rovnaký vnútorný stav.

Za predpokladu, že by sme chceli vytvoriť peňažný objekt nasledovne:

public class MutableMoney {súkromné ​​dlhé množstvo; súkromná reťazcová mena; verejné MutableMoney (dlhá suma, mena reťazca) {this.amount = suma; this.currency = currency; } // štandardní zakladatelia a zakladatelia}

Môžeme na ňom spustiť nasledujúci test na otestovanie jeho rovnosti:

@Test public void givenTwoSameValueMoneyObjects_whenEqualityTestFails_thenCorrect () {MutableMoney m1 = nový MutableMoney (10 000, "USD"); MutableMoney m2 = nový MutableMoney (10 000, „USD“); assertFalse (m1.equals (m2)); }

Všimnite si sémantiku testu.

Považujeme to za prekonané, keď si dva objekty peňazí nie sú rovnaké. To je preto, že neprepísali sme rovná sa metóda takže rovnosť sa meria porovnaním pamäťových referencií objektov, ktoré sa samozrejme nebudú líšiť, pretože ide o rôzne objekty zaberajúce rôzne pamäťové miesta.

Každý objekt predstavuje 10 000 USD, ale Java nám hovorí, že naše peňažné objekty nie sú rovnaké. Chceme, aby dva objekty testovali nerovnaké iba vtedy, keď sú buď iné sumy mien, alebo rôzne typy mien.

Teraz vytvorme objekt ekvivalentnej hodnoty a tentokrát necháme IDE vygenerovať väčšinu kódu:

verejná finálna trieda ImmutableMoney {súkromná finálna dlhá suma; súkromná konečná reťazcová mena; public ImmutableMoney (dlhá suma, mena reťazca) {this.amount = suma; this.currency = currency; } @Override public int hashCode () {final int prime = 31; int výsledok = 1; result = prime * result + (int) (suma ^ (suma >>> 32)); result = prime * result + ((currency == null)? 0: currency.hashCode ()); návratový výsledok; } @Override public boolean equals (Object obj) {if (this == obj) return true; if (obj == null) return false; if (getClass ()! = obj.getClass ()) return false; ImmutableMoney other = (ImmutableMoney) obj; if (amount! = other.amount) return false; if (currency == null) {if (other.currency! = null) return false; } else if (! currency.equals (other.currency)) return false; návrat pravdivý; }}

Jediný rozdiel je v tom, že sme preťažili rovná sa (Objekt) a hashCode () metódy, teraz máme kontrolu nad tým, ako chceme, aby Java porovnávala naše peňažné objekty. Spustíme jeho ekvivalentný test:

@Test public void givenTwoSameValueMoneyValueObjects_whenEqualityTestPasses_thenCorrect () {ImmutableMoney m1 = nový ImmutableMoney (10 000, "USD"); ImmutableMoney m2 = nové ImmutableMoney (10 000, „USD“); assertTrue (m1.equals (m2)); }

Všimnite si sémantiku tohto testu, očakávame, že prejde, keď budú obidva peňažné objekty testované rovnako cez rovná sa metóda.

4. Prečo AutoValue?

Teraz, keď dôkladne rozumieme typom hodnôt a prečo ich potrebujeme, sa môžeme pozrieť na AutoValue a na to, ako to prichádza do rovnice.

4.1. Problémy s ručným kódovaním

Keď vytvoríme typy hodnôt, aké sme vytvorili v predchádzajúcej časti, narazíme na množstvo problémov, ktoré s tým súvisia zlý dizajn a veľa štandardného kódu.

Trieda dvoch polí bude mať 9 riadkov kódu: jeden na deklaráciu balíka, dva na podpis triedy a jej uzatváraciu zátvorku, dva na deklarácie polí, dva pre konštruktory a jeho zatváraciu zátvorku a dva na inicializáciu polí, ale potom potrebujeme getry pre polia, z ktorých každý zaberá ďalšie tri riadky kódu a robí tak šesť ďalších riadkov.

Prekonanie hashCode () a equalTo (objekt) Metódy vyžadujú asi 9 riadkov a 18 riadkov, ktoré potláčajú natiahnuť() metóda pridáva ďalších päť riadkov.

To znamená, že by si našla dobre naformátovanú kódovú základňu pre našu triedu dvoch polí asi 50 riadkov kódu.

4.2 IDE na záchranu?

Toto je ľahké s IDE ako Eclipse alebo IntilliJ a s vytvorením iba jednej alebo dvoch tried s typom hodnoty. Popremýšľajte, aké množstvo takýchto tried je možné vytvoriť, bolo by to stále také ľahké, aj keď nám IDE pomáha?

Rýchly posun vpred, niekoľko mesiacov po ceste, predpokladajme, že sa musíme vrátiť k nášmu kódexu a vykonať zmeny a doplnenia Peniaze triedy a možno konvertovať mena pole z String typu na iný volaný typ hodnoty Mena.

4.3 IDE nie sú naozaj také užitočné

IDE ako Eclipse pre nás nemôže jednoducho upravovať naše prístupové metódy ani natiahnuť(), hashCode () alebo rovná sa (Objekt) metódy.

Toto refaktorovanie by sa muselo vykonať ručne. Úprava kódu zvyšuje potenciál pre chyby a s každým novým poľom, ktoré pridáme do Peniaze triedy sa počet riadkov exponenciálne zvyšuje.

Uvedomenie si skutočnosti, že k tomuto scenáru dochádza, že sa stáva často a vo veľkých objemoch, nás prinúti skutočne oceniť úlohu AutoValue.

5. Príklad AutoValue

Problém, ktorý AutoValue rieši, je vyradiť všetok štandardný kód, o ktorom sme hovorili v predchádzajúcej časti, z cesty, aby sme ho nikdy nemuseli písať, upravovať alebo dokonca čítať.

Pozrime sa na to isté Peniaze napríklad, ale tentokrát s AutoValue. Túto triedu budeme volať AutoValueMoney kvôli konzistencii:

@AutoValue verejná abstraktná trieda AutoValueMoney {verejný abstrakt String getCurrency (); public abstract long getAmount (); public static AutoValueMoney create (Mena reťazca, dlhá suma) {return new AutoValue_AutoValueMoney (mena, suma); }}

Stalo sa to, že napíšeme abstraktnú triedu, definujeme pre ňu abstraktné prístupové objekty, ale žiadne polia, anotujeme triedu @AutoValue - spolu iba 8 riadkov kódu a - javac generuje pre nás konkrétnu podtriedu, ktorá vyzerá takto:

verejná konečná trieda AutoValue_AutoValueMoney rozširuje AutoValueMoney {súkromná konečná reťazcová mena; konečná súkromná dlhá suma; AutoValue_AutoValueMoney (reťazcová mena, dlhé množstvo) {if (mena == null) hodiť novú NullPointerException (mena); this.currency = currency; this.amount = suma; } // štandardné getre @ Override public int hashCode () {int h = 1; h * = 1000003; h ^ = currency.hashCode (); h * = 1000003; h ^ = suma; návrat h; } @Override public boolean equals (Object o) {if (o == this) {return true; } if (o instanceof AutoValueMoney) {AutoValueMoney that = (AutoValueMoney) o; return (this.currency.equals (that.getCurrency ())) && (this.amount == that.getAmount ()); } return false; }}

S touto triedou nikdy nemusíme narábať vôbec priamo, nemusíme ju ani upravovať, keď potrebujeme pridať viac polí alebo vykonať zmeny v našich poliach, napríklad mena scenár v predchádzajúcej časti.

Javac pre nás vždy vygeneruje aktualizovaný kód.

Pri použití tohto nového typu hodnoty sú všetci volajúci iba nadradeným typom, ako to uvidíme v nasledujúcich testoch jednotiek.

Tu je test, ktorý overuje, či sú naše polia nastavené správne:

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

Test na overenie týchto dvoch AutoValueMoney Nasledujú objekty s rovnakou menou a rovnakou hodnotou testu:

@Test public void given2EqualValueTypesWithAutoValue_whenEqual_thenCorrect () {AutoValueMoney m1 = AutoValueMoney.create ("USD", 5 000); AutoValueMoney m2 = AutoValueMoney.create ("USD", 5 000); assertTrue (m1.equals (m2)); }

Keď zmeníme typ meny jedného peňažného objektu na GBP, test: 5 000 GBP == 5 000 USD už neplatí:

@Test public void given2DifferentValueTypesWithAutoValue_whenNotEqual_thenCorrect () {AutoValueMoney m1 = AutoValueMoney.create ("GBP", 5 000); AutoValueMoney m2 = AutoValueMoney.create ("USD", 5 000); assertFalse (m1.equals (m2)); }

6. Automatická hodnota so staviteľmi

Počiatočný príklad, na ktorý sme sa pozreli, pokrýva základné použitie funkcie AutoValue pomocou metódy statickej továrne ako nášho verejného API na vytváranie.

Všimnite si, že keby všetky naše polia boli Struny, bolo by ľahké ich zameniť, keď sme ich prešli na statickú továrenskú metódu, napríklad položením čiastka v mieste mena a naopak.

Toto je obzvlášť pravdepodobné, ak máme veľa polí a všetky sú String typu. Tento problém sa zhoršuje skutočnosťou, že s funkciou AutoValue všetky polia sa inicializujú cez konštruktor.

Na vyriešenie tohto problému by sme mali použiť staviteľ vzor. Našťastie. to môže byť generované AutoValue.

Naša trieda AutoValue sa v skutočnosti príliš nemení, až na to, že statická továrenská metóda je nahradená staviteľom:

@AutoValue verejná abstraktná trieda AutoValueMoneyWithBuilder {verejný abstrakt String getCurrency (); public abstract long getAmount (); static Builder builder () {return new AutoValue_AutoValueMoneyWithBuilder.Builder (); } @ AutoValue.Builder abstraktná statická trieda Builder {abstrakt Builder setCurrency (reťazcová mena); abstrakt Builder setAmount (dlhé množstvo); abstrakt AutoValueMoneyWithBuilder build (); }}

Vygenerovaná trieda je rovnaká ako tá prvá, ale generuje sa konkrétna vnútorná trieda pre staviteľa, ktorá do implementácie implementuje aj abstraktné metódy:

statická finálna trieda Builder rozširuje AutoValueMoneyWithBuilder.Builder {súkromná mena reťazca; súkromná dlhá suma; Builder () {} Builder (zdroj AutoValueMoneyWithBuilder) {this.currency = source.getCurrency (); this.amount = source.getAmount (); } @Override public AutoValueMoneyWithBuilder.Builder setCurrency (reťazcová mena) {this.currency = mena; vráťte to; } @Override public AutoValueMoneyWithBuilder.Builder setAmount (dlhé množstvo) {this.amount = suma; vráťte to; } @Override public AutoValueMoneyWithBuilder build () {chýba reťazec = ""; if (currency == null) {chýba + = "mena"; } if (suma == 0) {chýba + = "suma"; } if (! missing.isEmpty ()) {throw new IllegalStateException ("Missing required properties:" + missing); } vratit novu AutoValue_AutoValueMoneyWithBuilder (this.currency, this.amount); }}

Všimnite si tiež, ako sa výsledky testov nemenia.

Ak chceme vedieť, že hodnoty poľa sú skutočne správne nastavené prostredníctvom nástroja na tvorbu, môžeme vykonať tento test:

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

Testovanie rovnosti závisí od vnútorného stavu:

@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.equals (m2)); }

A keď sú hodnoty poľa odlišné:

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

7. Záver

V tomto tutoriáli sme predstavili väčšinu základných informácií o knižnici AutoValue spoločnosti Google a o tom, ako ju používať na vytváranie typov hodnôt s veľmi malým počtom kódov z našej strany.

Alternatívou k AutoValue od Googlu je projekt Lombok - tu sa môžete pozrieť na úvodný článok o používaní Lomboku.

Plnú implementáciu všetkých týchto príkladov a útržkov kódu nájdete v projekte AutoValue GitHub.


$config[zx-auto] not found$config[zx-overlay] not found