Objekte in Java vergleichen

1. Einleitung

Der Vergleich von Objekten ist ein wesentliches Merkmal objektorientierter Programmiersprachen.

In diesem Tutorial werden einige der Funktionen der Java-Sprache vorgestellt, mit denen wir Objekte vergleichen können. Darüber hinaus werden wir uns solche Funktionen in externen Bibliotheken ansehen.

2. == und ! = Operatoren

Beginnen wir mit den Operatoren == und ! = , Die erkennen können, ob zwei Java-Objekte gleich sind oder nicht.

2.1. Primitive

Gleich zu sein bedeutet für primitive Typen, gleiche Werte zu haben:

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

Dank des automatischen Unboxing funktioniert dies auch beim Vergleichen eines primitiven Werts mit seinem Wrapper-Typ-Gegenstück :

Integer a = new Integer(1); assertThat(1 == a).isTrue();

Wenn zwei Ganzzahlen unterschiedliche Werte haben, würde der Operator == false zurückgeben , während der Operator ! = True zurückgeben würde .

2.2. Objekte

Angenommen, wir möchten zwei Integer- Wrapper-Typen mit demselben Wert vergleichen:

Integer a = new Integer(1); Integer b = new Integer(1); assertThat(a == b).isFalse();

Durch den Vergleich zweier Objekte ist der Wert dieser Objekte nicht 1. Vielmehr unterscheiden sich ihre Speicheradressen im Stapel , da beide Objekte mit dem neuen Operator erstellt wurden. Wenn wir a bis b zugewiesen hätten, hätten wir ein anderes Ergebnis erzielt:

Integer a = new Integer(1); Integer b = a; assertThat(a == b).isTrue();

Nun wollen wir sehen, was passiert, wenn wir die Factory-Methode Integer # valueOf verwenden:

Integer a = Integer.valueOf(1); Integer b = Integer.valueOf(1); assertThat(a == b).isTrue();

In diesem Fall werden sie als gleich angesehen. Dies liegt daran, dass die valueOf () -Methode die Ganzzahl in einem Cache speichert , um zu vermeiden, dass zu viele Wrapper-Objekte mit demselben Wert erstellt werden. Daher gibt die Methode für beide Aufrufe dieselbe Integer- Instanz zurück.

Java macht dies auch für String :

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

Wenn sie jedoch mit dem neuen Operator erstellt würden, wären sie nicht dieselben.

Schließlich werden zwei Nullreferenzen als gleich angesehen, während jedes Nicht- Null- Objekt als von Null verschieden betrachtet wird :

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

Natürlich kann das Verhalten der Gleichheitsoperatoren einschränkend sein. Was ist, wenn wir zwei Objekte vergleichen möchten, die unterschiedlichen Adressen zugeordnet sind und dennoch aufgrund ihrer internen Zustände als gleich angesehen werden? Wir werden in den nächsten Abschnitten sehen, wie.

3. Objekt # entspricht Methode

Lassen Sie uns nun über ein umfassenderes Konzept der Gleichheit mit der Methode equals () sprechen .

Diese Methode ist in der Object- Klasse so definiert, dass jedes Java-Objekt sie erbt. Standardmäßig vergleicht die Implementierung die Objektspeicheradressen, sodass sie genauso funktioniert wie der Operator == . Wir können diese Methode jedoch überschreiben, um zu definieren, was Gleichheit für unsere Objekte bedeutet.

Lassen Sie uns zunächst sehen, wie es sich für vorhandene Objekte wie Integer verhält :

Integer a = new Integer(1); Integer b = new Integer(1); assertThat(a.equals(b)).isTrue();

Die Methode gibt weiterhin true zurück , wenn beide Objekte identisch sind.

Wir sollten beachten, dass wir ein Nullobjekt als Argument der Methode übergeben können, aber natürlich nicht als das Objekt, für das wir die Methode aufrufen.

Wir können die Methode equals () mit einem eigenen Objekt verwenden. Nehmen wir an, wir haben eine Personenklasse :

public class Person { private String firstName; private String lastName; public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }

Wir können die equals () -Methode für diese Klasse überschreiben , damit wir zwei Personen anhand ihrer internen Details vergleichen können:

@Override public boolean equals(Object o)  if (this == o) return true; if (o == null 

Weitere Informationen finden Sie in unserem Artikel zu diesem Thema.

4. Objekte # entspricht der statischen Methode

Schauen wir uns nun die Methode Objects # equals static an. Wir haben bereits erwähnt, dass wir null nicht als Wert des ersten Objekts verwenden können, da sonst eine NullPointerException ausgelöst wird.

Die equals () -Methode der Objects- Hilfsklasse löst diese Probleme. Es werden zwei Argumente verwendet und verglichen, wobei auch Nullwerte verarbeitet werden.

Lassen Sie uns vergleichen Person Objekte wieder mit:

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

Wie bereits erwähnt, verarbeitet die Methode Nullwerte . Wenn beide Argumente null sind , wird true zurückgegeben . Wenn nur eines von ihnen null ist , wird false zurückgegeben .

Das kann sehr praktisch sein. Angenommen, wir möchten unserer Personenklasse ein optionales Geburtsdatum hinzufügen :

public Person(String firstName, String lastName, LocalDate birthDate) { this(firstName, lastName); this.birthDate = birthDate; }

Dann würden wir unsere aktualisieren müssen equals () Methode , aber mit null Handhabung. Wir könnten dies tun, indem wir diese Bedingung zu unserer Methode equals () hinzufügen :

birthDate == null ? that.birthDate == null : birthDate.equals(that.birthDate);

Wenn wir unserer Klasse jedoch viele nullbare Felder hinzufügen, kann dies sehr unübersichtlich werden. Die Verwendung der Objects # equals- Methode in unserer equals () -Implementierung ist viel sauberer und verbessert die Lesbarkeit:

Objects.equals(birthDate, that.birthDate);

5. Vergleichbare Schnittstelle

Comparison logic can also be used to place objects in a specific order. The Comparable interface allows us to define an ordering between objects, by determining if an object is greater, equal, or lesser than another.

The Comparable interface is generic and has only one method, compareTo(), which takes an argument of the generic type and returns an int. The returned value is negative if this is lower than the argument, 0 if they are equal, and positive otherwise.

Let's say, in our Person class, we want to compare Person objects by their last name:

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

The compareTo() method will return a negative int if called with a Person having a greater last name than this, zero if the same last name, and positive otherwise.

For more information, take a look at our article about this topic.

6. Comparator Interface

The Comparator interface is generic and has a compare method that takes two arguments of that generic type and returns an integer. We already saw that pattern earlier with the Comparable interface.

Comparator is similar; however, it's separated from the definition of the class. Therefore, we can define as many Comparators we want for a class, where we can only provide one Comparable implementation.

Let's imagine we have a web page displaying people in a table view, and we want to offer the user the possibility to sort them by first names rather than last names. It isn't possible with Comparable if we also want to keep our current implementation, but we could implement our own Comparators.

Let's create a PersonComparator that will compare them only by their first names:

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

Let's now sort a List of people using that Comparator:

Person joe = new Person("Joe", "Portman"); Person allan = new Person("Allan", "Dale"); List people = new ArrayList(); people.add(joe); people.add(allan); people.sort(compareByFirstNames); assertThat(people).containsExactly(allan, joe);

There are other methods on the Comparator interface we can use in our compareTo() implementation:

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

In this case, we are first comparing last names, then first names. Then, we compare birth dates but as they are nullable we must say how to handle that so we give a second argument telling they should be compared according to their natural order but with null values going last.

7. Apache Commons

Let's now take a look at the Apache Commons library. First of all, let's import the Maven dependency:

 org.apache.commons commons-lang3 3.10 

7.1. ObjectUtils#notEqual Method

First, let's talk about the ObjectUtils#notEqual method. It takes two Object arguments, to determine if they are not equal, according to their own equals() method implementation. It also handles null values.

Let's reuse our String examples:

String a = new String("Hello!"); String b = new String("Hello World!"); assertThat(ObjectUtils.notEqual(a, b)).isTrue(); 

It should be noted that ObjectUtils has an equals() method. However, that's deprecated since Java 7, when Objects#equals appeared

7.2. ObjectUtils#compare Method

Now, let's compare object order with the ObjectUtils#compare method. It's a generic method that takes two Comparable arguments of that generic type and returns an Integer.

Let's see that using Strings again:

String first = new String("Hello!"); String second = new String("How are you?"); assertThat(ObjectUtils.compare(first, second)).isNegative();

By default, the method handles null values by considering them as greater. It offers an overloaded version that offers to invert that behavior and consider them lesser, taking a boolean argument.

8. Guava

Now, let's take a look at Guava. First of all, let's import the dependency:

 com.google.guava guava 29.0-jre 

8.1. Objects#equal Method

Similar to the Apache Commons library, Google provides us with a method to determine if two objects are equal, Objects#equal. Though they have different implementations, they return the same results:

String a = new String("Hello!"); String b = new String("Hello!"); assertThat(Objects.equal(a, b)).isTrue();

Though it's not marked as deprecated, the JavaDoc of this method says that it should be considered as deprecated since Java 7 provides the Objects#equals method.

8.2. Comparison Methods

Now, the Guava library doesn't offer a method to compare two objects (we'll see in the next section what we can do to achieve that though), but it does provide us with methods to compare primitive values. Let's take the Ints helper class and see how its compare() method works:

assertThat(Ints.compare(1, 2)).isNegative();

As usual, it returns an integer that may be negative, zero, or positive if the first argument is lesser, equal, or greater than the second, respectively. Similar methods exist for all the primitive types, except for bytes.

8.3. ComparisonChain Class

Finally, the Guava library offers the ComparisonChain class that allows us to compare two objects through a chain of comparisons. We can easily compare two Person objects by the first and last names:

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

The underlying comparison is achieved using the compareTo() method, so the arguments passed to the compare() methods must either be primitives or Comparables.

9. Conclusion

In diesem Artikel haben wir uns die verschiedenen Möglichkeiten zum Vergleichen von Objekten in Java angesehen. Wir haben den Unterschied zwischen Gleichheit, Gleichheit und Ordnung untersucht. Wir haben uns auch die entsprechenden Funktionen in den Bibliotheken Apache Commons und Guava angesehen.

Wie üblich finden Sie den vollständigen Code für diesen Artikel auf GitHub.