Sammenligning af objekter i Java

1. Introduktion

Sammenligning af objekter er et væsentligt træk ved objektorienterede programmeringssprog.

I denne vejledning skal vi se på nogle af funktionerne i Java-sproget, der giver os mulighed for at sammenligne objekter. Derudover vil vi se på sådanne funktioner i eksterne biblioteker.

2. == og != Operatører

Lad os begynde med == og != operatører, der kan fortælle, om to Java-objekter er ens eller ikke.

2.1. Primitiver

For primitive typer betyder det at have det samme at have samme værdier:

assertThat (1 == 1) .isTrue ();

Takket være auto-unboxing dette fungerer også, når man sammenligner en primitiv værdi med dens indpakningstype modstykke:

Heltal a = nyt heltal (1); assertThat (1 == a) .isTrue ();

Hvis to heltal har forskellige værdier, vises == operatøren ville vende tilbage falsk, mens != operatøren ville vende tilbage rigtigt.

2.2. Objekter

Lad os sige, at vi vil sammenligne to Heltal indpakningstyper med samme værdi:

Heltal a = nyt heltal (1); Heltal b = nyt heltal (1); hævder, at (a == b) .isFalse ();

Ved at sammenligne to objekter, værdien af ​​disse objekter er ikke 1. Det er snarere deres hukommelsesadresser i stakken der er forskellige, da begge objekter blev oprettet ved hjælp af ny operatør. Hvis vi havde tildelt -en til b, så ville vi have haft et andet resultat:

Heltal a = nyt heltal (1); Heltal b = a; assertThat (a == b) .isTrue ();

Lad os nu se, hvad der sker, når vi bruger Heltal # værdiOf fabriksmetode:

Heltal a = Heltal.værdiOf (1); Heltal b = Heltal.værdiOf (1); assertThat (a == b) .isTrue ();

I dette tilfælde betragtes de som de samme. Dette skyldes, at Værdi af() metoden gemmer Heltal i en cache for at undgå at oprette for mange indpakningsobjekter med samme værdi. Derfor returnerer metoden den samme Heltal eksempel for begge opkald.

Java gør dette også for Snor:

assertThat ("Hello!" == "Hello!"). isTrue ();

Men hvis de blev oprettet ved hjælp af ny operatør, så ville de ikke være de samme.

Langt om længe, to nul referencer betragtes som de samme, mens enhver ikke-nul objekt betragtes som forskelligt fra nul:

assertThat (null == null) .isTrue (); assertThat ("Hej!" == null) .isFalse ();

Naturligvis kan ligestillingsoperatørernes opførsel være begrænsende. Hvad hvis vi vil sammenligne to objekter kortlagt til forskellige adresser og alligevel have dem betragtet som ensartede baseret på deres interne tilstande? Vi ser hvordan i de næste sektioner.

3. Objekt # er lig Metode

Lad os nu tale om et bredere begreb om lighed med lige med() metode.

Denne metode er defineret i Objekt klasse, så hvert Java-objekt arver det. Som standard, dens implementering sammenligner objekthukommelsesadresser, så det fungerer det samme som == operatør. Vi kan dog tilsidesætte denne metode for at definere, hvad lighed betyder for vores objekter.

Lad os først se, hvordan det opfører sig for eksisterende objekter som Heltal:

Heltal a = nyt heltal (1); Heltal b = nyt heltal (1); hævder, at (a. ligning (b)). er sandt ();

Metoden vender stadig tilbage rigtigt når begge objekter er ens.

Vi skal bemærke, at vi kan passere en nul objekt som argumentet for metoden, men selvfølgelig ikke som det objekt, vi kalder metoden på.

Vi kan bruge lige med() metode med et eget objekt. Lad os sige, at vi har en Person klasse:

offentlig klasse person {privat streng fornavn; privat streng efternavn; offentlig person (streng fornavn, streng efternavn) {this.firstName = fornavn; this.lastName = efternavn; }}

Vi kan tilsidesætte lige med() metode til denne klasse, så vi kan sammenligne to Persons baseret på deres interne detaljer:

@Override offentlige boolske er lig med (Objekt o) hvis (dette == o) returnerer sandt; hvis (o == null 

For mere information, se vores artikel om dette emne.

4. Objekter # er lig med Statisk metode

Lad os nu se på Objekter # er lig med statisk metode. Vi nævnte tidligere, at vi ikke kan bruge nul som værdien af ​​det første objekt ellers a NullPointerException ville blive kastet.

Det lige med() metode til Objekter hjælperklasse løser disse problemer. Det tager to argumenter og sammenligner dem, også håndtering nul værdier.

Lad os sammenligne Person genstande igen med:

Person joe = ny person ("Joe", "Portman"); Person joeAgain = ny person ("Joe", "Portman"); Person natalie = ny person ("Natalie", "Portman"); assertThat (Objects.equals (joe, joeAgain)). isTrue (); assertThat (Objects.equals (joe, natalie)). isFalse ();

Som vi sagde, håndterer metoden nul værdier. Derfor, hvis begge argumenter er nul det vender tilbage rigtigt, og hvis kun en af ​​dem er det nul, det vender tilbage falsk.

Dette kan være rigtig praktisk. Lad os sige, at vi vil føje en valgfri fødselsdato til vores Person klasse:

offentlig person (streng fornavn, streng efternavn, LocalDate fødselsdato) {dette (fornavn, efternavn); this.birthDate = fødselsdato; }

Derefter bliver vi nødt til at opdatere vores lige med() metode men med nul håndtering. Vi kunne gøre dette ved at tilføje denne betingelse til vores lige med() metode:

fødselsdato == null? that.birthDate == null: birthDate.equals (that.birthDate);

Men hvis vi tilføjer mange ugyldige felter til vores klasse, kan det blive virkelig rodet. Bruger Objekter # er lig med metode i vores lige med() implementering er meget renere og forbedrer læsbarheden:

Objects.equals (birthDate, that.birthDate);

5. Sammenlignelig Interface

Sammenligningslogik kan også bruges til at placere objekter i en bestemt rækkefølge. Det Sammenlignelig interface giver os mulighed for at definere en rækkefølge mellem objekter, ved at bestemme, om et objekt er større, lige eller mindre end et andet.

Det Sammenlignelig interface er generisk og har kun en metode, sammenligne med(), som tager et argument af den generiske type og returnerer et int. Den returnerede værdi er negativ, hvis det her er lavere end argumentet, 0 hvis de er lige, og ellers positive.

Lad os sige, i vores Person klasse, vi vil sammenligne Person objekter ved deres efternavn:

public class Person implementer Comparable {// ... @Override public int compareTo (Person o) {return this.lastName.compareTo (o.lastName); }}

Det sammenligne med() metode returnerer et negativt int hvis kaldes med en Person har et større efternavn end det her, nul, hvis det samme efternavn og ellers positivt.

For mere information, se vores artikel om dette emne.

6. Komparator Interface

Det Komparator interface er generisk og har en sammenligne metode, der tager to argumenter af den generiske type og returnerer en heltal. Vi så allerede dette mønster tidligere med Sammenlignelig interface.

Komparator er ens; dog er det adskilt fra definitionen af ​​klassen. Derfor, vi kan definere så mange Komparatorer vi ønsker en klasse, hvor vi kun kan tilbyde en Sammenlignelig implementering.

Lad os forestille os, at vi har en webside, der viser folk i en tabelvisning, og vi vil give brugeren mulighed for at sortere dem efter fornavne snarere end efternavne. Det er ikke muligt med Sammenlignelig hvis vi også vil beholde vores nuværende implementering, men vi kunne implementere vores egen Komparatorer.

Lad os oprette en PersonKomparator der kun sammenligner dem med deres fornavne:

Comparator CompareByFirstNames = Comparator.comparing (Person :: getFirstName);

Lad os nu sortere en Liste af mennesker, der bruger det Komparator:

Person joe = ny person ("Joe", "Portman"); Person allan = ny person ("Allan", "Dale"); Liste over mennesker = ny ArrayList (); people.add (joe); people.add (allan); people.sort (sammenlignByFirstNames); hævder, at (mennesker). indeholder eksakt (allan, joe);

Der er andre metoder på Komparator interface, vi kan bruge i vores sammenligne med() implementering:

@Override public int CompareTo (Person o) {return Comparator.comparing (Person :: getLastName) .thenComparing (Person :: getFirstName) .thenComparing (Person :: getBirthDate, Comparator.nullsLast (Comparator.naturalOrder ())) .compare ( dette, o); }

I dette tilfælde sammenligner vi først efternavne og derefter fornavne. Derefter sammenligner vi fødselsdatoer, men da de er ugyldige, skal vi sige, hvordan vi skal håndtere det, så vi giver et andet argument, der fortæller, at de skal sammenlignes i henhold til deres naturlige rækkefølge, men med nul værdier går sidst.

7. Apache Commons

Lad os nu se på Apache Commons-biblioteket. Lad os først og fremmest importere Maven-afhængigheden:

 org.apache.commons commons-lang3 3.10 

7.1. ObjectUtils # notEqual Metode

Lad os først tale om ObjectUtils # notEqual metode. Det tager to Objekt argumenter for at afgøre, om de ikke er lige, i henhold til deres egne lige med() metodeimplementering. Det håndterer også nul værdier.

Lad os genbruge vores Snor eksempler:

Streng a = ny streng ("Hej!"); Streng b = ny streng ("Hello World!"); assertThat (ObjectUtils.notEqual (a, b)). isTrue (); 

Det skal bemærkes, at ObjectUtils har en lige med() metode. Det er imidlertid udfaset siden Java 7, hvornår Objekter # er lig med dukkede op

7.2. ObjectUtils # sammenlign Metode

Lad os nu sammenligne objektrækkefølge med ObjectUtils # sammenlign metode. Det er en generisk metode, der tager to Sammenlignelig argumenter af den generiske type og returnerer en Heltal.

Lad os se det ved hjælp af Strenge igen:

Streng først = ny streng ("Hej!"); Streng sekund = ny streng ("Hvordan har du det?"); assertThat (ObjectUtils.compare (første, anden)). er Negativ ();

Metoden håndteres som standard nul værdier ved at betragte dem som større. Det tilbyder en overbelastet version, der tilbyder at vende denne adfærd og betragte dem mindre, idet de tager en boolsk argument.

8. Guava

Lad os nu se på Guava. Lad os først og fremmest importere afhængigheden:

 com.google.guava guava 29.0-jre 

8.1. Objekter # lige Metode

I lighed med Apache Commons-biblioteket giver Google os en metode til at afgøre, om to objekter er ens, Objekter # lige. Selvom de har forskellige implementeringer, returnerer de de samme resultater:

Streng a = ny streng ("Hej!"); Streng b = ny streng ("Hej!"); assertThat (Objects.equal (a, b)). isTrue ();

Selvom det ikke er markeret som udfaset, siger JavaDoc i denne metode, at det skal betragtes som udfaset, da Java 7 giver Objekter # er lig med metode.

8.2. Sammenligningsmetoder

Nu tilbyder Guava-biblioteket ikke en metode til at sammenligne to objekter (vi ser i næste afsnit, hvad vi dog kan gøre for at opnå det), men det giver os metoder til at sammenligne primitive værdier. Lad os tage Ints hjælperklasse og se, hvordan dens sammenligne() metode fungerer:

hævder, at (Ints. sammenligne (1, 2)). er negativ ();

Som normalt returnerer den en heltal det kan være negativt, nul eller positivt, hvis det første argument er henholdsvis mindre, lige eller større end det andet. Lignende metoder findes for alle de primitive typer undtagen bytes.

8.3. Sammenligningskæde Klasse

Endelig tilbyder Guava biblioteket Sammenligningskæde klasse, der giver os mulighed for at sammenligne to objekter gennem en kæde af sammenligninger. Vi kan let sammenligne to Person objekter med for- og efternavne:

Person natalie = ny person ("Natalie", "Portman"); Person joe = ny person ("Joe", "Portman"); int comparisonResult = ComparisonChain.start (). sammenligne (natalie.getLastName (), joe.getLastName ()). sammenligne (natalie.getFirstName (), joe.getFirstName ()) .result (); assertThat (comparisonResult) .isPositive ();

Den underliggende sammenligning opnås ved hjælp af sammenligne med() metode, så argumenterne overføres til sammenligne() metoder skal enten være primitive eller Sammenligneligs.

9. Konklusion

I denne artikel så vi på de forskellige måder at sammenligne objekter i Java på. Vi undersøgte forskellen mellem lighed, lighed og orden. Vi kiggede også på de tilsvarende funktioner i Apache Commons og Guava-bibliotekerne.

Som sædvanlig kan den fulde kode til denne artikel findes på GitHub.