Introduktion til AutoValue

1. Oversigt

AutoValue er en kildekodegenerator til Java, og mere specifikt er det et bibliotek til generere kildekode til værdiobjekter eller værditypede objekter.

For at generere et værditypeobjekt skal du bare gøre det kommentere en abstrakt klasse med @AutoValue kommentar og kompilér din klasse. Hvad der genereres er et værdiobjekt med accessormetoder, parametreret konstruktør, korrekt tilsidesat toString (), er lig med (Object) og hashCode () metoder.

Følgende kodestykke er et hurtigt eksempel af en abstrakt klasse, der, når den kompileres, vil resultere i et værdiobjekt med navnet AutoVærdi_Person.

@AutoValue abstrakt klasse Person {static Person create (String name, int age) {return new AutoValue_Person (name, age); } abstrakt Strengnavn (); abstrakt alder (); } 

Lad os fortsætte og finde ud af mere om værdiobjekter, hvorfor vi har brug for dem, og hvordan AutoValue kan hjælpe med at gøre opgaven med at generere og omlægge kode meget mindre tidskrævende.

2. Maven-opsætning

For at bruge AutoValue i et Maven-projekt skal du medtage følgende afhængighed i pom.xml:

 com.google.auto.value auto-værdi 1.2 

Den seneste version kan findes ved at følge dette link.

3. Værdityperede objekter

Værdityper er slutproduktet af biblioteket, så for at sætte pris på dets plads i vores udviklingsopgaver skal vi grundigt forstå værdityper, hvad de er, hvad de ikke er, og hvorfor vi har brug for dem.

3.1. Hvad er værdityper?

Værditype-objekter er objekter, hvis lighed med hinanden ikke bestemmes af identitet, men snarere deres interne tilstand. Dette betyder, at to forekomster af et værditypet objekt betragtes som lige så længe de har lige feltværdier.

Typisk er værdityper uforanderlige. Deres felter skal laves endelig og de må ikke have setter metoder, da dette vil gøre dem ændrede efter instantiering.

De skal forbruge alle feltværdier gennem en konstruktør eller en fabriksmetode.

Værdityper er ikke JavaBeans, fordi de ikke har en standard- eller nul-argumentkonstruktør, og de har heller ikke settermetoder, de er ikke dataoverførselsobjekter eller almindelige gamle Java-objekter.

Derudover skal en værditypet klasse være endelig, så de ikke kan udvides, mindst at nogen tilsidesætter metoderne. JavaBeans, DTO'er og POJO'er behøver ikke være endelige.

3.2. Oprettelse af en værditype

Forudsat at vi vil oprette en værditype kaldet Foo med felter kaldet tekst og nummer. Hvordan ville vi gå rundt med det?

Vi ville lave en sidste klasse og markere alle dens felter som endelige. Så ville vi bruge IDE til at generere konstruktøren, hashCode () metode, den er lig med (Objekt) metode, den getters som obligatoriske metoder og a toString () metode, og vi ville have en klasse som sådan:

offentlig finaleklasse Foo {privat final Strengtekst; privat endeligt int-nummer; public Foo (String text, int number) {this.text = text; dette.nummer = antal; } // standard getters @ Override public int hashCode () {return Objects.hash (text, number); } @ Override public String toString () {return "Foo [text =" + text + ", number =" + number + "]"; } @ Override offentlige boolske lig (Objekt obj) {hvis (dette == obj) returnerer sandt; hvis (obj == null) returnerer false; hvis (getClass ()! = obj.getClass ()) returnerer false; Foo andet = (Foo) obj; hvis (nummer! = andet.nummer) returnerer falsk; if (text == null) {if (other.text! = null) returner false; } ellers hvis (! text.equals (other.text)) {return false; } returner sandt }}

Efter oprettelse af en forekomst af FooVi forventer, at dets interne tilstand forbliver den samme i hele sin livscyklus.

Som vi vil se i det følgende underafsnit det hashCode af et objekt skal skifte fra forekomst til forekomst, men for værdityper skal vi binde det til de felter, der definerer den interne tilstand for værdiobjektet.

Derfor ville selv ændring af et felt med det samme objekt ændre hashCode værdi.

3.3. Sådan fungerer værdityper

Årsagen til, at værdityper skal være uforanderlige, er at forhindre enhver ændring af deres interne tilstand af applikationen, efter at de er blevet instantificeret.

Når vi ønsker at sammenligne to objekter med værdi, vi skal derfor bruge er lig med (Objekt) metode til Objekt klasse.

Dette betyder, at vi altid skal tilsidesætte denne metode i vores egne værdityper og kun returnere true, hvis felterne på de værdiobjekter, vi sammenligner, har samme værdier.

Desuden for os at bruge vores værdiobjekter i hash-baserede samlinger som HashSets og HashMaps uden at gå i stykker, vi skal implementere ordentligt hashCode () metode.

3.4. Hvorfor vi har brug for værdityper

Behovet for værdityper kommer ofte op. Dette er tilfælde, hvor vi gerne vil tilsidesætte originalens standardadfærd Objekt klasse.

Som vi allerede ved, er standardimplementeringen af Objekt klasse betragter to objekter lige, når de dog har den samme identitet til vores formål betragter vi to objekter ens, når de har den samme interne tilstand.

Forudsat at vi gerne vil oprette et pengeobjekt som følger:

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

Vi kan køre følgende test på den for at teste dens lighed:

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

Bemærk testens semantik.

Vi anser det for at være passeret, når de to pengeobjekter ikke er ens. Dette er fordi vi har ikke tilsidesat lige med metode så lighed måles ved at sammenligne hukommelsesreferencerne for objekterne, som naturligvis ikke vil være forskellige, fordi de er forskellige objekter, der optager forskellige hukommelsessteder.

Hvert objekt repræsenterer 10.000 USD men Java fortæller os, at vores pengeobjekter ikke er ens. Vi ønsker, at de to objekter kun skal teste ulige, når enten valutabeløbene er forskellige, eller valutatyperne er forskellige.

Lad os nu oprette et ækvivalent værdi-objekt, og denne gang vil vi lade IDE generere det meste af koden:

offentlig endelig klasse ImmutableMoney {privat endelig langt beløb; privat endelig streng valuta; public ImmutableMoney (langt beløb, strengvaluta) {this.amount = beløb; denne.valuta = valuta; } @ Override public int hashCode () {final int prime = 31; int resultat = 1; resultat = prime * resultat + (int) (beløb ^ (beløb >>> 32)); resultat = prime * resultat + ((valuta == null)? 0: currency.hashCode ()); returresultat } @ Override offentlige boolske lig (Objekt obj) {hvis (dette == obj) returnerer sandt; hvis (obj == null) returnerer false; hvis (getClass ()! = obj.getClass ()) returnerer false; ImmutableMoney other = (ImmutableMoney) obj; hvis (beløb! = andet.beløb) returnerer falsk; if (currency == null) {if (other.currency! = null) returner false; } ellers hvis (! currency.equals (other.currency)) returnerer falsk; returner sandt; }}

Den eneste forskel er, at vi overstyrer er lig med (Objekt) og hashCode () metoder, nu har vi kontrol over, hvordan vi vil have Java til at sammenligne vores pengeobjekter. Lad os køre dens ækvivalente test:

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

Læg mærke til semantikken ved denne test, vi forventer, at den vil bestå, når begge pengeobjekter tester lige via lige med metode.

4. Hvorfor AutoValue?

Nu hvor vi grundigt forstår værdityper, og hvorfor vi har brug for dem, kan vi se på AutoValue og hvordan det kommer ind i ligningen.

4.1. Problemer med håndkodning

Når vi opretter værdityper, som vi har gjort i det foregående afsnit, løber vi ind i en række problemer relateret til dårligt design og en masse kedelpladekode.

En to feltklasse vil have 9 kodelinjer: en til pakkeerklæring, to til klassesignaturen og dens lukkebøjle, to til feltdeklarationer, to til konstruktører og dens lukkebøjle og to til initialisering af felterne, men så har vi brug for getters for felterne, der hver tager yderligere tre linjer kode, hvilket gør seks ekstra linjer.

Tilsidesættelse af hashCode () og equalTo (Objekt) Metoder kræver henholdsvis ca. 9 og 18 linjer og tilsidesætter toString () metoden tilføjer yderligere fem linjer.

Det betyder, at en velformateret kodebase for vores to feltklasser ville tage ca. 50 linier kode.

4.2 IDE'er til redning?

Dette er let med en IDE som Eclipse eller IntilliJ og med kun en eller to værditypede klasser at oprette. Tænk på et væld af sådanne klasser at skabe, ville det stadig være så let, selvom IDE hjælper os?

Hurtigt frem, nogle måneder nede ad vejen, antager, at vi er nødt til at revidere vores kode og foretage ændringer til vores Penge klasser og måske konvertere betalingsmiddel felt fra Snor skriv til en anden værditype kaldet Betalingsmiddel.

4.3 IDE'er er ikke rigtig så nyttige

En IDE som Eclipse kan ikke bare redigere vores tilgangsmetoder for os toString (), hashCode () eller er lig med (Objekt) metoder.

Denne refactoring skulle udføres manuelt. Redigering af kode øger potentialet for fejl og med hvert nyt felt, vi tilføjer til Penge klasse stiger antallet af linjer eksponentielt.

At erkende det faktum, at dette scenarie sker, at det sker ofte og i store mængder, får os til at værdsætte rollen som AutoValue.

5. Eksempel på automatisk værdi

Problemet, som AutoValue løser, er at tage al kogepladekoden, som vi talte om i det foregående afsnit, ud af vores måde, så vi aldrig behøver at skrive den, redigere den eller endda læse den.

Vi vil se på det samme Penge eksempel, men denne gang med AutoValue. Vi kalder denne klasse AutoVærdipenge af hensyn til konsistens:

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

Hvad der er sket er, at vi skriver en abstrakt klasse, definerer abstrakte accessors til den, men ingen felter, vi kommenterer klassen med @AutoValue alt i alt kun 8 linjer kode, og javac genererer en konkret underklasse til os, der ser sådan ud:

offentlig endelig klasse AutoValue_AutoValueMoney udvider AutoValueMoney {privat endelig strengvaluta; privat endeligt langt beløb AutoValue_AutoValueMoney (strengvaluta, langt beløb) {hvis (valuta == null) kaster nyt NullPointerException (valuta); denne.valuta = valuta; dette.beløb = beløb; } // standard getters @ Override public int hashCode () {int h = 1; h * = 1000003; h ^ = currency.hashCode (); h * = 1000003; h ^ = beløb; returnere h; } @ Override offentlige boolske er lig (Objekt o) {hvis (o == dette) {returner sandt; } hvis (o forekomst af AutoValueMoney) {AutoValueMoney that = (AutoValueMoney) o; return (this.currency.equals (that.getCurrency ())) && (this.amount == that.getAmount ()); } returner falsk; }}

Vi behøver aldrig at behandle denne klasse direkte, og vi skal heller ikke redigere den, når vi skal tilføje flere felter eller foretage ændringer i vores felter som f.eks. betalingsmiddel scenarie i det foregående afsnit.

Javac vil altid gendanne opdateret kode for os.

Når du bruger denne nye værditype, er alle opkaldere, der kun er den overordnede type, som vi vil se i de følgende enhedstests.

Her er en test, der bekræfter, at vores felter er indstillet korrekt:

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

En test for at kontrollere, at to AutoVærdipenge objekter med samme valuta og samme beløbstest svarer til følgende:

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

Når vi ændrer valutatypen for et pengeobjekt til GBP, testes: 5000 GBP == 5000 USD er ikke længere sandt:

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

6. AutoVærdi med bygherrer

Det indledende eksempel, vi har set på, dækker den grundlæggende brug af AutoValue ved hjælp af en statisk fabriksmetode som vores offentlige oprettelses-API.

Bemærk, at hvis alle vores felter var Strenge, det ville være let at udveksle dem, da vi sendte dem til den statiske fabriksmetode, som at placere beløb i stedet for betalingsmiddel og omvendt.

Dette vil især ske, hvis vi har mange felter, og alle er af Snor type. Dette problem forværres af det faktum, at med AutoValue, alle felter initialiseres gennem konstruktøren.

For at løse dette problem skal vi bruge Bygger mønster. Heldigvis. dette kan genereres af AutoValue.

Vores AutoValue-klasse ændrer sig ikke meget, bortset fra at den statiske fabriksmetode erstattes af en bygherre:

@ AutoValue offentlig abstrakt klasse AutoValueMoneyWithBuilder {offentlig abstrakt String getCurrency (); offentlig abstrakt lang getAmount (); statisk Builder-bygherre () {returner ny AutoValue_AutoValueMoneyWithBuilder.Builder (); } @ AutoValue.Builder abstrakt statisk klasse Builder {abstrakt Builder setCurrency (strengvaluta); abstrakt Builder setAmount (lang mængde); abstrakt AutoValueMoneyWithBuilder build (); }}

Den genererede klasse er nøjagtig den samme som den første, men der genereres en konkret indre klasse for bygherren og implementerer også de abstrakte metoder i bygherren:

statisk slutklasse Builder udvider AutoValueMoneyWithBuilder.Builder {privat strengvaluta; privat langt beløb Builder () {} Builder (AutoValueMoneyWithBuilder source) {this.currency = source.getCurrency (); this.amount = source.getAmount (); } @ Override offentlige AutoValueMoneyWithBuilder.Builder setCurrency (strengvaluta) {this.currency = valuta; returner dette; } @ Override offentlige AutoValueMoneyWithBuilder.Builder setAmount (langt beløb) {this.amount = beløb; returner dette; } @ Override offentlig AutoValueMoneyWithBuilder build () {String missing = ""; hvis (valuta == null) {mangler + = "valuta"; } hvis (beløb == 0) {mangler + = "beløb"; } if (! missing.isEmpty ()) {throw new IllegalStateException ("Manglende krævede egenskaber:" + mangler); } returner nye AutoValue_AutoValueMoneyWithBuilder (denne valuta, denne mængde); }}

Bemærk også, hvordan testresultaterne ikke ændres.

Hvis vi vil vide, at feltværdierne faktisk er korrekt indstillet gennem bygherren, kan vi udføre denne test:

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

At teste, at lighed afhænger af intern tilstand:

@Test offentlig ugyldighed 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)); }

Og når feltværdierne er forskellige:

@Test offentlig ugyldighed 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. Konklusion

I denne vejledning har vi introduceret det meste af det grundlæggende i Googles AutoValue-bibliotek, og hvordan man bruger det til at skabe værdityper med en meget lille kode fra vores side.

Et alternativ til Googles AutoValue er Lombok-projektet - du kan se den indledende artikel om brug af Lombok her.

Den fulde implementering af alle disse eksempler og kodestykker findes i AutoValue GitHub-projektet.