Introduksjon til AutoValue

1. Oversikt

AutoValue er en kildekodegenerator for Java, og mer spesifikt er det et bibliotek for generere kildekode for verdiobjekter eller verditypede objekter.

For å generere et verditypeobjekt er alt du trenger å gjøre kommentere en abstrakt klasse med @AutoValue kommentar og kompiler klassen din. Det som genereres er et verdiobjekt med tilgangsmetoder, parameterisert konstruktør, riktig overstyrt toString (), er lik (Object) og hashCode () metoder.

Følgende kodebit er et raskt eksempel av en abstrakt klasse som når den kompileres vil resultere i et verdiobjekt navngitt AutoValue_Person.

@AutoValue abstrakt klasse Person {statisk person opprette (strengnavn, alder) {returner nytt AutoValue_Person (navn, alder); } abstrakt Strengnavn (); abstrakt alder (); } 

La oss fortsette og finne ut mer om verdiobjekter, hvorfor vi trenger dem og hvordan AutoValue kan bidra til å gjøre oppgaven med å generere og omorganisere kode mye mindre tidkrevende.

2. Maven-oppsett

For å bruke AutoValue i et Maven-prosjekt, må du inkludere følgende avhengighet i pom.xml:

 com.google.auto.value auto-value 1.2 

Den siste versjonen finner du ved å følge denne lenken.

3. Verditypede objekter

Verdityper er sluttproduktet til biblioteket, så for å sette pris på dets plass i utviklingsoppgavene våre, må vi grundig forstå verdityper, hva de er, hva de ikke er og hvorfor vi trenger dem.

3.1. Hva er verdityper?

Verditypeobjekter er objekter hvis likhet med hverandre ikke bestemmes av identitet, men snarere deres interne tilstand. Dette betyr at to forekomster av et verditypet objekt betraktes som like så lenge de har like feltverdier.

Verdityper er vanligvis uforanderlige. Feltene deres må lages endelig og de må ikke ha setter metoder da dette vil gjøre dem endrede etter instantiering.

De må konsumere alle feltverdier gjennom en konstruktør eller en fabrikkmetode.

Verdityper er ikke JavaBeans fordi de ikke har en standard- eller nullargumentkonstruktør, og de har heller ikke settermetoder, de er ikke dataoverføringsobjekter eller vanlige gamle Java-objekter.

I tillegg må en verditypet klasse være endelig, slik at de ikke kan utvides, i det minste at noen overstyrer metodene. JavaBeans, DTO og POJO trenger ikke være endelige.

3.2. Opprette en verditype

Forutsatt at vi ønsker å lage en verditype kalt Foo med felt kalt tekst og Nummer. Hvordan skulle vi gjøre det?

Vi skulle lage en siste klasse og merke alle feltene som endelige. Så ville vi bruke IDE til å generere konstruktøren, hashCode () metoden, den er lik (Objekt) metoden, den getters som obligatoriske metoder og a toString () metode, og vi vil ha en klasse som så:

offentlig finaleklasse Foo {privat final Strengtekst; privat endelig int nummer; offentlig Foo (strengtekst, int-nummer) {this.text = tekst; dette.nummer = antall; } // standard getters @ Override public int hashCode () {return Objects.hash (text, number); } @ Override public String toString () {return "Foo [text =" + text + ", number =" + number + "]"; } @Override offentlig boolsk er lik (Objekt obj) {hvis (denne == obj) returnerer sann; hvis (obj == null) returnerer false; hvis (getClass ()! = obj.getClass ()) returnerer false; Foo other = (Foo) obj; hvis (nummer! = annet.nummer) returnerer falsk; if (text == null) {if (other.text! = null) returner false; } annet hvis (! text.equals (other.text)) {return false; } returner sant; }}

Etter å ha opprettet en forekomst av Foo, vi forventer at den interne tilstanden vil være den samme i hele livssyklusen.

Som vi vil se i det følgende underavsnittet de hashCode av et objekt må endres fra forekomst til forekomst, men for verdityper, må vi knytte den til feltene som definerer den interne tilstanden til verdiobjektet.

Derfor vil endring av et felt med det samme objektet endre på hashCode verdi.

3.3. Hvordan verdityper fungerer

Årsaken til at verdityper må være uforanderlige, er å forhindre enhver endring i deres interne tilstand av applikasjonen etter at de er instantiert.

Når vi vil sammenligne to verdityperte objekter, vi må derfor bruke er lik (Objekt) metoden for Gjenstand klasse.

Dette betyr at vi alltid må overstyre denne metoden i våre egne verdityper og bare returnere true hvis feltene til verdiobjektene vi sammenligner har like verdier.

Videre, for oss å bruke våre verdiobjekter i hash-baserte samlinger som HashSets og HashMaps uten å bryte, vi må implementere hashCode () metode.

3.4. Hvorfor vi trenger verdityper

Behovet for verdityper kommer ganske ofte opp. Dette er tilfeller der vi vil overstyre standardoppførselen til originalen Gjenstand klasse.

Som vi allerede vet, er standardimplementeringen av Gjenstand klasse anser to objekter like når de har samme identitet, men for våre formål anser vi to objekter like når de har samme indre tilstand.

Forutsatt at vi ønsker å lage et pengeobjekt som følger:

offentlig klasse MutableMoney {privat langt beløp; privat streng valuta; offentlig MutableMoney (langt beløp, strengvaluta) {this.amount = beløp; denne.valuta = valuta; } // standard getters and setters}

Vi kan kjøre følgende test for å teste dens likhet:

@Test offentlig ugyldighet gittTwoSameValueMoneyObjects_whenEqualityTestFails_thenCorrect () {MutableMoney m1 = ny MutableMoney (10000, "USD"); MutableMoney m2 = ny MutableMoney (10000, "USD"); assertFalse (m1.equals (m2)); }

Legg merke til testens semantikk.

Vi anser det som passert når de to pengeobjektene ikke er like. Dette er fordi vi har ikke overstyrt er lik metode så likhet måles ved å sammenligne minnereferansene til objektene, som selvfølgelig ikke kommer til å være forskjellige fordi de er forskjellige objekter som opptar forskjellige minneplasseringer.

Hvert objekt representerer 10.000 USD, men Java forteller oss at pengene våre ikke er like. Vi vil at de to objektene bare skal teste ulik når enten valutaens beløp er forskjellige eller valutatypene er forskjellige.

La oss nå lage et tilsvarende verdiobjekt, og denne gangen lar vi IDE generere mesteparten av koden:

offentlig sluttklasse ImmutableMoney {privat endelig langt beløp; privat slutt String valuta; offentlig ImmutableMoney (langt beløp, strengvaluta) {this.amount = beløp; denne.valuta = valuta; } @ Override public int hashCode () {final int prime = 31; int resultat = 1; resultat = prim * resultat + (int) (beløp ^ (beløp >>> 32)); resultat = prime * resultat + ((valuta == null)? 0: currency.hashCode ()); returresultat; } @Override offentlig boolsk er lik (Objekt obj) {hvis (denne == obj) returnerer sann; hvis (obj == null) returnerer false; hvis (getClass ()! = obj.getClass ()) returnerer false; ImmutablePoney annet = (ImmutablePoney) obj; hvis (beløp! = annet.beløp) returnerer falsk; if (currency == null) {if (other.currency! = null) returner false; } annet hvis (! currency.equals (other.currency)) returnerer falsk; returner sant; }}

Den eneste forskjellen er at vi overstyrer er lik (Objekt) og hashCode () metoder, nå har vi kontroll over hvordan vi vil at Java skal sammenligne pengene våre. La oss kjøre den tilsvarende testen:

@Test offentlig ugyldig givenTwoSameValueMoneyValueObjects_whenEqualityTestPasses_thenCorrect () {ImmutableMoney m1 = new ImmutableMoney (10000, "USD"); ImmutableMoney m2 = nye ImmutableMoney (10000, "USD"); assertTrue (m1.equals (m2)); }

Legg merke til semantikken til denne testen, vi forventer at den skal bestå når begge pengerobjekter tester likt via er lik metode.

4. Hvorfor AutoValue?

Nå som vi grundig forstår verdityper og hvorfor vi trenger dem, kan vi se på AutoValue og hvordan det kommer inn i ligningen.

4.1. Problemer med håndkoding

Når vi oppretter verdityper som vi har gjort i forrige avsnitt, vil vi støte på en rekke problemer knyttet til dårlig design og mye kjeleplatekode.

En tofeltklasse vil ha 9 linjer med kode: en for pakkeerklæring, to for klassesignaturen og dens lukkende brakett, to for feltdeklarasjoner, to for konstruktører og dens lukkende brace og to for initialisering av feltene, men da trenger vi getters for feltene, hver tar tre flere linjer med kode, og lager seks ekstra linjer.

Overstyring av hashCode () og equalTo (Objekt) metoder krever henholdsvis 9 linjer og 18 linjer og overstyrer toString () metoden legger til ytterligere fem linjer.

Det betyr at en godt formatert kodebase for klassen vår i to felt vil ta omtrent 50 linjer med kode.

4.2 IDEer til unnsetning?

Dette er enkelt med en IDE som Eclipse eller IntilliJ og med bare en eller to verditypede klasser å lage. Tenk på et mangfold av slike klasser å lage, ville det fortsatt være like enkelt selv om IDE hjelper oss?

Spol fremover, noen måneder på veien, antar at vi må se på koden vår og gjøre endringer i vår Penger klasser og kanskje konvertere valuta felt fra String skriv til en annen verditype kalt Valuta.

4.3 IDEer er ikke veldig nyttige

En IDE som Eclipse kan ikke bare redigere våre tilgangsmetoder eller toString (), hashCode () eller er lik (Objekt) metoder.

Denne refactoring må gjøres for hånd. Redigering av kode øker potensialet for feil og med hvert nye felt vi legger til Penger klasse øker antall linjer eksponentielt.

Å erkjenne det faktum at dette scenariet skjer, at det skjer ofte og i store volumer, vil få oss til å sette stor pris på rollen som AutoValue.

5. Eksempel på automatisk verdi

Problemet AutoValue løser er å ta all kjeleplatekoden som vi snakket om i forrige avsnitt, ut av vår måte, slik at vi aldri trenger å skrive den, redigere den eller til og med lese den.

Vi vil se på det samme Penger eksempel, men denne gangen med AutoValue. Vi vil kalle denne klassen AutoValuePenger av hensyn til konsistens:

@ AutoValue offentlig abstrakt klasse AutoValueMoney {offentlig abstrakt String getCurrency (); offentlig abstrakt lang getAmount (); offentlig statisk AutoValueMoney opprette (strengvaluta, langt beløp) {returner nytt AutoValue_AutoValueMoney (valuta, beløp); }}

Det som har skjedd er at vi skriver en abstrakt klasse, definerer abstrakte aksessorer for den, men ingen felt, vi kommenterer klassen med @AutoValue alt til sammen bare 8 linjer med kode, og javac genererer en konkret underklasse for oss som ser slik ut:

offentlig sluttklasse AutoValue_AutoValueMoney utvider AutoValueMoney {private final String currency; privat endelig langt beløp; AutoValue_AutoValueMoney (strengvaluta, langt beløp) {if (valuta == null) kaster nytt NullPointerException (valuta); denne.valuta = valuta; dette.beløp = beløp; } // standard getters @Override public int hashCode () {int h = 1; h * = 1000003; h ^ = currency.hashCode (); h * = 1000003; h ^ = beløp; retur h; } @ Override offentlig boolsk er lik (Objekt o) {hvis (o == dette) {returner sant; } if (o instanceof AutoValueMoney) {AutoValueMoney that = (AutoValueMoney) o; return (this.currency.equals (that.getCurrency ())) && (this.amount == that.getAmount ()); } returner falsk; }}

Vi trenger aldri å håndtere denne klassen direkte i det hele tatt, og vi trenger heller ikke å redigere den når vi trenger å legge til flere felt eller gjøre endringer i feltene våre som valuta scenario i forrige avsnitt.

Javac vil alltid regenerere oppdatert kode for oss.

Når du bruker denne nye verditypen, ser alle innringere bare foreldretypen som vi vil se i de følgende enhetstestene.

Her er en test som bekrefter at feltene våre blir satt riktig:

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

En test for å verifisere de to AutoValuePenger objekter med samme valuta og samme beløpstest lik følger:

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

Når vi endrer valutatypen for ett pengeobjekt til GBP, testen: 5000 GBP == 5000 USD er ikke lenger sant:

@Test offentlig ugyldighet gitt2DifferentValueTypesWithAutoValue_whenNotEqual_thenCorrect () {AutoValueMoney m1 = AutoValueMoney.create ("GBP", 5000); AutoValueMoney m2 = AutoValueMoney.create ("USD", 5000); assertFalse (m1.equals (m2)); }

6. AutoValue With Builders

Det første eksemplet vi har sett på, dekker den grunnleggende bruken av AutoValue ved hjelp av en statisk fabrikkmetode som vårt API for offentlig opprettelse.

Legg merke til at hvis alle våre felt var Strenger, det ville være enkelt å bytte dem når vi sendte dem til den statiske fabrikkmetoden, som å plassere beløp i stedet for valuta og vice versa.

Dette vil sannsynligvis skje hvis vi har mange felt og alle er av String type. Dette problemet blir verre av det faktum at med AutoValue, alle felt initialiseres gjennom konstruktøren.

For å løse dette problemet bør vi bruke bygger mønster. Heldigvis. dette kan genereres av AutoValue.

AutoValue-klassen vår endres egentlig ikke mye, bortsett fra at den statiske fabrikkmetoden erstattes av en byggherre:

@ AutoValue offentlig abstrakt klasse AutoValueMoneyWithBuilder {offentlig abstrakt String getCurrency (); offentlig abstrakt lang getAmount (); statisk byggmester () {returner ny AutoValue_AutoValueMoneyWithBuilder.Builder (); } @ AutoValue.Builder abstrakt statisk klasse Builder {abstrakt Builder setCurrency (strengvaluta); abstrakt Builder setAmount (lang mengde); abstrakt AutoValueMoneyWithBuilder build (); }}

Den genererte klassen er akkurat den samme som den første, men det genereres en konkret indre klasse for byggherren og implementerer også de abstrakte metodene i byggherren:

statisk sluttklasse Builder utvider AutoValueMoneyWithBuilder.Builder {privat strengvaluta; privat langt beløp; Builder () {} Builder (AutoValueMoneyWithBuilder source) {this.currency = source.getCurrency (); this.amount = source.getAmount (); } @ Override offentlige AutoValueMoneyWithBuilder.Builder setCurrency (strengvaluta) {this.currency = currency; returner dette; } @ Override offentlige AutoValueMoneyWithBuilder.Builder setAmount (langt beløp) {this.amount = beløp; returner dette; } @Override offentlig AutoValueMoneyWithBuilder build () {String missing = ""; hvis (valuta == null) {mangler + = "valuta"; } hvis (beløp == 0) {mangler + = "beløp"; } if (! missing.isEmpty ()) {kast ny IllegalStateException ("Mangler nødvendige egenskaper:" + mangler); } returner nye AutoValue_AutoValueMoneyWithBuilder (denne valutaen, denne mengden); }}

Legg også merke til hvordan testresultatene ikke endres.

Hvis vi vil vite at feltverdiene faktisk er riktig satt gjennom byggherren, kan vi utføre denne testen:

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

For å teste at likhet avhenger av intern tilstand:

@Test offentlig ugyldighet gitt2EqualValueTypesWithBuilder_whenEqual_thenCorrect () {AutoValueMoneyWithBuilder m1 = AutoValueMoneyWithBuilder.builder () .setAmount (5000) .setCurrency ("USD"). Build (); AutoValueMoneyWithBuilder m2 = AutoValueMoneyWithBuilder.builder () .setAmount (5000) .setCurrency ("USD"). Build (); assertTrue (m1.equals (m2)); }

Og når feltverdiene er forskjellige:

@Test offentlig ugyldighet gitt2DifferentValueTypesBuilder_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. Konklusjon

I denne opplæringen har vi introdusert det meste av det grunnleggende i Googles AutoValue-bibliotek og hvordan du bruker det til å lage verdityper med veldig lite kode fra vår side.

Et alternativ til Googles AutoValue er Lombok-prosjektet - du kan ta en titt på den innledende artikkelen om bruk av Lombok her.

Fullstendig implementering av alle disse eksemplene og kodebitene finner du i AutoValue GitHub-prosjektet.


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