Uvod u AutoValue

1. Pregled

AutoValue je generator izvornog koda za Javu, a preciznije je knjižnica za generiranje izvornog koda za vrijednosne objekte ili vrijednosno tipizirane objekte.

Sve što trebate učiniti je da biste generirali objekt vrijednosnog tipa dodavanje apstraktne klase znakom @AutoValue bilješka i sastavite svoj razred. Generira se objekt vrijednosti s pristupnim metodama, parametriziranim konstruktorom, pravilno nadjačanim toString (), jednako (Objekt) i hashCode () metode.

Sljedeći isječak koda je brzi primjer apstraktne klase koja će pri kompajliranju rezultirati imenovanim objektom vrijednosti AutoValue_Person.

@AutoValue apstraktna klasa Person {static Person create (String name, int age) {return new AutoValue_Person (ime, dob); } sažetak Ime niza (); sažetak int age (); } 

Nastavimo i saznajmo više o vrijednosnim objektima, zašto su nam potrebni i kako AutoValue može učiniti zadatak generiranja i refaktoriranja koda mnogo manje dugotrajnim.

2. Postavljanje Mavena

Da biste koristili AutoValue u projektima Maven, morate uključiti sljedeću ovisnost u pom.xml:

 com.google.auto.value auto-value 1.2 

Najnoviju verziju možete pronaći slijedeći ovu poveznicu.

3. Vrijednosno tipizirani objekti

Vrijednosni tipovi krajnji su proizvod knjižnice, pa da bismo uvažili njegovo mjesto u našim razvojnim zadacima, moramo temeljito razumjeti vrijednosne tipove, što oni jesu, što nisu i zašto su nam potrebni.

3.1. Što su vrijednosne vrste?

Objekti vrijednosnog tipa su objekti čija međusobna jednakost nije određena identitetom već njihovim unutarnjim stanjem. To znači da se dva primjerka objekta tipom vrijednosti smatraju jednakim sve dok imaju jednake vrijednosti polja.

Tipovi vrijednosti su obično nepromjenjivi. Njihova polja moraju biti napravljena konačni a ne smiju imati seter metode jer će ih to učiniti promjenjivima nakon instanciranja.

Moraju potrošiti sve vrijednosti polja putem konstruktora ili tvorničke metode.

Vrste vrijednosti nisu JavaBeans jer nemaju zadani ili nulti konstruktor argumenata, a nemaju niti metode postavljanja, slično, oni nisu objekti za prijenos podataka niti obični stari Java objekti.

Uz to, klasa upisana u vrijednost mora biti konačna, tako da se ne može proširiti, barem da netko nadjača metode. JavaBeans, DTO i POJO ne moraju biti konačni.

3.2. Stvaranje vrste vrijednosti

Pod pretpostavkom da želimo stvoriti tip vrijednosti tzv Foo s poljima zvanim tekst i broj. Kako bismo to poduzeli?

Napravili bismo završni razred i sva njegova polja označili kao konačna. Tada bismo koristili IDE za generiranje konstruktora, hashCode () metoda, jednako (objekt) metoda, geteri kao obvezne metode i a toString () metodu, a mi bismo imali klasu poput ove:

javni završni razred Foo {privatni završni Tekst niza; privatni konačni int broj; javni Foo (Tekst niza, int broj) {this.text = text; this.number = broj; } // standardni getteri @Override public int hashCode () {return Objects.hash (tekst, broj); } @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 ostalo = (Foo) obj; if (broj! = drugi.broj) vrati false; if (text == null) {if (other.text! = null) return false; } else if (! text.equals (other.text)) {return false; } return true; }}

Nakon stvaranja instance Foo, očekujemo da će unutarnje stanje ostati isto tijekom cijelog životnog ciklusa.

Kao što ćemo vidjeti u sljedećem pododjeljku the hashCode objekta mora se mijenjati od instance do instance, ali za tipove vrijednosti moramo ga povezati s poljima koja definiraju unutarnje stanje objekta vrijednosti.

Stoga bi čak i promjena polja istog objekta promijenila i hashCode vrijednost.

3.3. Kako funkcioniraju vrijednosne vrste

Razlog zbog kojeg tipovi vrijednosti moraju biti nepromjenjivi je da spriječi bilo kakvu promjenu njihovog unutarnjeg stanja od strane aplikacije nakon instanciranja.

Kad god želimo usporediti bilo koja dva vrijednosno upisana objekta, moramo, dakle, koristiti jednako (objekt) metoda Objekt razred.

To znači da ovu metodu moramo uvijek nadjačati u vlastitim vrstama vrijednosti i vratiti true samo ako polja objekata vrijednosti koje uspoređujemo imaju jednake vrijednosti.

Štoviše, za nas da koristimo svoje vrijednosne objekte u zbirkama temeljenim na raspršivanju HashSets i HashMaps bez prekida, moramo pravilno provesti hashCode () metoda.

3.4. Zašto su nam potrebne vrijednosti

Potreba za vrijednosnim vrstama javlja se prilično često. To su slučajevi u kojima bismo htjeli nadjačati zadano ponašanje originala Objekt razred.

Kao što već znamo, zadana implementacija Objekt klasa međutim smatra dva predmeta jednakima kada imaju isti identitet u svoje svrhe dva predmeta smatramo jednakima kada imaju isto unutarnje stanje.

Pod pretpostavkom da bismo željeli stvoriti novčani objekt kako slijedi:

javna klasa MutableMoney {privatni dugačak iznos; privatna niska valuta; javni MutableMoney (dugačak iznos, valuta niza) {this.amount = iznos; this.currency = currency; } // standardni getteri i postavljači}

Na njemu možemo pokrenuti sljedeći test kako bismo testirali njegovu jednakost:

@Test javna praznina givenTwoSameValueMoneyObjects_whenEqualityTestFails_thenCorrect () {MutableMoney m1 = novi MutableMoney (10000, "USD"); MutableMoney m2 = novi MutableMoney (10000, "USD"); assertFalse (m1.equals (m2)); }

Primijetite semantiku testa.

Smatramo da je prošlo kad dva novčana predmeta nisu jednaka. Ovo je zbog nismo nadjačali jednako metoda tako se jednakost mjeri uspoređivanjem memorijskih referenci objekata, koje naravno neće biti različite, jer se radi o različitim objektima koji zauzimaju različita memorijska mjesta.

Svaki objekt predstavlja 10.000 USD, ali Java nam kaže da naši novčani objekti nisu jednaki. Želimo da se dva objekta testiraju nejednako samo kada su iznosi valuta različiti ili su vrste valuta različite.

Ajmo sada stvoriti objekt ekvivalentne vrijednosti i ovaj put ćemo pustiti IDE da generira većinu koda:

javni konačni razred ImmutableMoney {privatni konačni dugačak iznos; privatna konačna valuta niza; javni ImmutableMoney (dugačak iznos, valuta niza) {this.amount = iznos; this.currency = currency; } @Override public int hashCode () {final int prime = 31; rezultat int = 1; rezultat = osnovni * rezultat + (int) (iznos ^ (iznos >>> 32)); rezultat = osnovni * rezultat + ((valuta == null)? 0: currency.hashCode ()); povratni rezultat; } @Override public boolean equals (Object obj) {if (this == obj) return true; if (obj == null) return false; if (getClass ()! = obj.getClass ()) return false; Nepromjenjivi novac ostalo = (Nepromjenjivi novac) obj; if (iznos! = drugi. iznos) vrati false; if (currency == null) {if (other.currency! = null) return false; } inače if (! currency.equals (other.currency)) return false; povratak istinit; }}

Jedina je razlika što smo poništili jednako (objekt) i hashCode () metode, sada imamo kontrolu nad time kako želimo da Java uspoređuje naše novčane predmete. Izvršimo njegov ekvivalentni test:

@Test javna praznina givenTwoSameValueMoneyValueObjects_whenEqualityTestPasses_thenCorrect () {ImmutableMoney m1 = novi ImmutableMoney (10000, "USD"); ImmutableMoney m2 = novi ImmutableMoney (10000, "USD"); assertTrue (m1.equals (m2)); }

Primijetite semantiku ovog testa, očekujemo da će proći kada se oba novčana predmeta testiraju jednakim putem jednako metoda.

4. Zašto AutoValue?

Sad kad temeljito razumijemo vrste vrijednosti i zašto su nam potrebne, možemo pogledati AutoValue i kako to dolazi u jednadžbu.

4.1. Problemi s ručnim kodiranjem

Kada stvorimo vrste vrijednosti kao što smo to učinili u prethodnom odjeljku, naići ćemo na niz problema povezanih s loš dizajn i puno šifre uzorka.

Klasa s dva polja imat će 9 redaka koda: jedan za deklaraciju paketa, dva za potpis klase i njezinu završnu zagradu, dva za deklaracije polja, dva za konstruktore i završnu zagradu i dva za inicijalizaciju polja, ali tada nam trebaju getteri za polja, svako uzimajući još tri retka koda, čineći šest dodatnih redaka.

Nadjačavanje hashCode () i jednakTo (objekt) metode zahtijevaju oko 9, odnosno 18 redaka i nadjačavanje toString () metoda dodaje još pet redaka.

To znači da bi trebala biti dobro formatirana baza koda za naša dva polja oko 50 redaka koda.

4.2 IDE-ovi za spašavanje?

To je lako s IDE-om poput Eclipsea ili IntilliJ-a i sa samo jednom ili dvije klase upisane u vrijednosti koje treba stvoriti. Razmislite o mnoštvu takvih klasa za stvaranje, bi li to i dalje bilo lako čak i kad bi nam IDE pomogao?

Brzo naprijed, nekoliko mjeseci dalje, pretpostavimo da moramo ponovno pregledati svoj kodeks i unijeti izmjene u naš Novac klase i možda pretvoriti valuta polje od Niz tip u drugi tip vrijednosti koji se naziva Valuta.

4.3 IDE-i nisu stvarno korisni

IDE poput Eclipsea ne može nam jednostavno urediti naše metode pristupa niti toString (), hashCode () ili jednako (objekt) metode.

Ova refaktorizacija morala bi se obaviti ručno. Uređivanje koda povećava potencijalnu pogrešku i sa svakim novim poljem koje dodamo u Novac klase, broj linija raste eksponencijalno.

Prepoznajući činjenicu da se ovaj scenarij događa, da se događa često i u velikim količinama, učinit ćemo da zaista cijenimo ulogu AutoValuea.

5. Primjer automatske vrijednosti

Problem koji AutoValue rješava je uklanjanje cijelog koda o kojem smo govorili u prethodnom odjeljku, tako da ga nikada ne moramo pisati, uređivati ​​ili čak čitati.

I mi ćemo pogledati isto Novac na primjer, ali ovaj put s AutoValue. Nazvat ćemo ovaj razred AutoValueMoney radi dosljednosti:

@AutoValue javni sažetak klase AutoValueMoney {javni sažetak String getCurrency (); javni sažetak long getAmount (); javni statički AutoValueMoney create (String valuta, dugačak iznos) {return new AutoValue_AutoValueMoney (valuta, iznos); }}

Ono što se dogodilo je da napišemo apstraktnu klasu, definiramo apstraktne pristupnike za nju, ali bez polja, klasu označavamo s @AutoValue sve ukupno na samo 8 redaka koda, i javac generira za nas konkretnu podklasu koja izgleda ovako:

javna konačna klasa AutoValue_AutoValueMoney proširuje AutoValueMoney {private final String valuta; privatni konačni dugačak iznos; AutoValue_AutoValueMoney (Niz valute, dugačak iznos) {if (currency == null) izbaci novi NullPointerException (valuta); this.currency = currency; this.amount = iznos; } // standardni getteri @Override public int hashCode () {int h = 1; h * = 1000003; h ^ = currency.hashCode (); h * = 1000003; h ^ = iznos; povratak h; } @Override public boolean equals (Objekt o) {if (o == this) {return true; } if (o primjer AutoValueMoney) {AutoValueMoney that = (AutoValueMoney) o; return (this.currency.equals (that.getCurrency ())) && (this.amount == that.getAmount ()); } return false; }}

S ovom se klasom uopće nikada ne moramo baviti izravno, niti je moramo uređivati ​​kada trebamo dodati više polja ili unijeti promjene u naša polja poput valuta scenarij u prethodnom odjeljku.

Javac uvijek će obnoviti ažurirani kod za nas.

Dok koristimo ovaj novi tip vrijednosti, svi pozivi vide samo roditeljski tip kao što ćemo vidjeti u sljedećim jediničnim testovima.

Evo testa koji provjerava jesu li naša polja ispravno postavljena:

@Test javna praznina givenValueTypeWithAutoValue_whenFieldsCorrectSet_thenCorrect () {AutoValueMoney m = AutoValueMoney.create ("USD", 10000); assertEquals (m.getAmount (), 10000); assertEquals (m.getCurrency (), "USD"); }

Test za potvrdu te dvije AutoValueMoney slijede objekti s istom valutom i jednakim iznosom:

@Test javna praznina given2EqualValueTypesWithAutoValue_whenEqual_thenCorrect () {AutoValueMoney m1 = AutoValueMoney.create ("USD", 5000); AutoValueMoney m2 = AutoValueMoney.create ("USD", 5000); assertTrue (m1.equals (m2)); }

Kada promijenimo vrstu valute jednog novčanog predmeta u GBP, test: 5000 GBP == 5000 USD više nije istina:

@Test javna praznina given2DifferentValueTypesWithAutoValue_whenNotEqual_thenCorrect () {AutoValueMoney m1 = AutoValueMoney.create ("GBP", 5000); AutoValueMoney m2 = AutoValueMoney.create ("USD", 5000); assertFalse (m1.equals (m2)); }

6. AutoValue s graditeljima

Početni primjer koji smo pogledali obuhvaća osnovnu upotrebu AutoValue koristeći statičku tvorničku metodu kao naš API za javno stvaranje.

Primijetite da ako su sva naša polja Žice, bilo bi ih lako zamijeniti dok smo ih prebacivali na statičku tvorničku metodu, poput postavljanja iznos na mjestu valuta i obrnuto.

To će se osobito vjerojatno dogoditi ako imamo mnogo polja i sva su Niz tip. Ovaj problem pogoršava činjenica da s AutoValue, sva su polja inicijalizirana kroz konstruktor.

Da bismo riješili ovaj problem, trebali bismo koristiti graditelj uzorak. Srećom. ovo može generirati AutoValue.

Naša klasa AutoValue zapravo se ne mijenja puno, osim što statičku tvorničku metodu zamjenjuje graditelj:

@AutoValue javni sažetak klase AutoValueMoneyWithBuilder {javni sažetak String getCurrency (); javni sažetak long getAmount (); static Builder builder () {return new AutoValue_AutoValueMoneyWithBuilder.Builder (); } @ AutoValue.Builder apstraktna statička klasa Builder {abstract Builder setCurrency (string valuta); sažetak Graditelj setAmount (dugačak iznos); sažetak AutoValueMoneyWithBuilder build (); }}

Generirana klasa je ista kao i prva, ali generira se konkretna unutarnja klasa za graditelja, implementirajući apstraktne metode u graditelj:

static final grade Builder proširuje AutoValueMoneyWithBuilder.Builder {private String currency; privatni dugi iznos; Graditelj () {} Graditelj (izvor AutoValueMoneyWithBuilder) {this.currency = source.getCurrency (); this.amount = source.getAmount (); } @Override public AutoValueMoneyWithBuilder.Builder setCurrency (string valuta) {this.currency = currency; vrati ovo; } @Override public AutoValueMoneyWithBuilder.Builder setAmount (dugačak iznos) {this.amount = iznos; vrati ovo; } @Override public AutoValueMoneyWithBuilder build () {String missing = ""; if (currency == null) {nedostaje + = "valuta"; } if (iznos == 0) {nedostaje + = "iznos"; } if (! missing.isEmpty ()) {throw new IllegalStateException ("Nedostaju potrebna svojstva:" + nedostaje); } vrati novi AutoValue_AutoValueMoneyWithBuilder (this.currency, this.amount); }}

Također primijetite kako se rezultati ispitivanja ne mijenjaju.

Ako želimo znati da su vrijednosti polja zapravo ispravno postavljene kroz graditelj, možemo izvršiti ovaj test:

@Test javna praznina givenValueTypeWithBuilder_whenFieldsCorrectSet_thenCorrect () {AutoValueMoneyWithBuilder m = AutoValueMoneyWithBuilder.builder (). setAmount (5000) .setCurrency ("USD"). build (); assertEquals (m.getAmount (), 5000); assertEquals (m.getCurrency (), "USD"); }

Da biste testirali da jednakost ovisi o unutarnjem stanju:

@Test javna praznina 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 kada su vrijednosti polja različite:

@Test javna praznina 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. Zaključak

U ovom smo uputstvu predstavili većinu osnova Googleove knjižnice AutoValue i kako ga koristiti za stvaranje vrsta vrijednosti s vrlo malo koda s naše strane.

Alternativa Googleovom AutoValue je projekt Lombok - ovdje možete pogledati uvodni članak o korištenju Lomboka.

Potpuna implementacija svih ovih primjera i isječaka koda može se naći u projektu AutoValue GitHub.


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