Java Comparative Summary of Various Methods of Comparing Objects

  • 2021-09-20 20:36:24
  • OfStack

1. = = and! = operator

Let's start from = = and! = Operator that initially determines whether two Java objects are the same.

1.1 Original Type (Primitives)

For primitive types, identical means having equal values:


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

Thanks to automatic unpacking, you can also do this when comparing primitive values with their packaging type counterparts:


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

If the values of two integers are different, the == operator returns false, and! The = operator returns true.

1.2 Object comparison

Suppose we want to compare two integer wrapper types with the same value:


Integer a = new Integer(1);
Integer b = new Integer(1);

assertThat(a == b).isFalse();

By comparing two objects, the value of these objects is not 1, but their memory address on the stack, because both objects were created using the new operator. If we assign a to b, we will get different results:


Integer a = new Integer(1);
Integer b = a;

assertThat(a == b).isTrue();

Now, let's see what happens when we use the Integer # valueOf factory method:


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

assertThat(a == b).isTrue();

In this case, they are considered identical. This is because the valueOf () method stores integers in the cache to avoid creating too many wrapper objects with the same value. Therefore, this method returns the same integer instance for both calls.

It is also 1 for strings:


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

However, if they are created using the new operator, then they are not identical. Finally, two null references are considered identical, and any non-null object is considered different from a null object:


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

Of course, the behavior of equality operators may be limited. What if we wanted to compare two objects mapped to different addresses and treat them as equal based on their internal state? We will see how to do this in the next section.

2. equals method of Object

Now, let's discuss a broader concept of equality using the equals () method. This method is defined in the Object class so that every Java object inherits it. By default, its implementation compares object memory addresses, so it works the same way as the == operator. However, we can override this method to define what equality means to our objects.

First, let's look at how it behaves on existing objects, such as Integer:


Integer a = new Integer(1);
Integer b = new Integer(1);

assertThat(a.equals(b)).isTrue();

When two objects are the same, the method still returns true. We should note that we can pass an empty object as an argument to the method, but of course, not as the object to call the method. We can use the equals () method on our own objects. Suppose we have an Person class:


public class Person {
    private String firstName;
    private String lastName;

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

We can override the class's equals () method to compare the two people based on their internal details:


@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person that = (Person) o;
    return firstName.equals(that.firstName) &&
      lastName.equals(that.lastName);
}

3. Static method of Objects equals

Now let's look at the equals static method of Objects. As we mentioned earlier, null cannot be used as the value of the first object, otherwise NullPointerException will be thrown. The equals () method of the Objects helper class solves this problem. It accepts two parameters and compares them, while handling null values.

Let's compare Person objects again:


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

As mentioned earlier, this method handles null values. Therefore, if both parameters are null, true is returned; If only one parameter is null, false is returned. It's really convenient. Suppose we want to add an optional birth date to the Person class:


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

Then, we must update the equals () method, but deal with Null. We can add this condition to the equals () method:


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

However, if we add many fields to the class that can be null, it can become very confusing. Using the Objects # equals method in the equals () implementation is simpler and more readable:


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

4. Comparable interface

Comparison logic can also be used to sort objects. Comparable interfaces allow us to define the order between objects by determining whether one object is greater than, equal to, or less than another.

The Compariable interface is generic and has only one method, compareTo (), which accepts parameters of a generic type and returns int. If the current value is less than the parameter, a negative value is returned; Returns 0 if they are equal; Otherwise, a positive value is returned.

For example, in our Person class, we want to compare by the last name of the Person object:


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

If the compareTo () method is called with a person whose last name is greater than this, negative int is returned; Returns zero if the last name is the same; Otherwise, a positive int is returned.

5. Comparator interface

The Comparator interface is generic and has an compare method that accepts two parameters of the generic type and returns an integer. We have seen this pattern in the previous comparable interface.

Comparators are similar; However, it is separate from the definition of the class. Therefore, we can define any number of comparators for a class, of which we can only provide one comparable implementation.

Suppose we have a table showing people's information in a web page, and we want users to be able to sort them by first name instead of last name. It is not possible to use Comparable if we want to keep the current implementation, but we can implement our own comparator.

Let's create an Person Comparator that will be compared only by their name:


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

Now let's use this comparator to sort a group of people:


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

In the compareTo () implementation, you can use other methods on the Comparator interface:


@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 compare the last name first and then the first name. Then, we compare birth dates, but since they are nullable, we have to explain how to deal with them, so we give the second parameter, telling them that they should be compared according to their natural order, but null values are the last.

6. Using Apache Commons

Now let's look at the apachecommons library. First, let's import the Maven dependencies:


<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.11</version>
</dependency>

6.1 notEqual Method for ObjectUtils

First, let's discuss the ObjectUtils # notEqual method. It requires two object parameters to determine whether they are equal according to their own equals () method implementation. It also handles null values.

Let's reuse our string example:


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

It should be noted that ObjectUtils has one equals () method. However, this method has been abandoned since Objects # equals appeared in Java 7.

6.2 compare Method for ObjectUtils

Now, let's compare the order of objects using the ObjectUtils # compare method. It is a generic method that accepts two comparable parameters of the generic type and returns an integer.

Let's look at how to use strings again:


String first = new String("Hello!");
String second = new String("How are you?");

assertThat(ObjectUtils.compare(first, second)).isNegative();

By default, this method handles null values by treating them as larger values. It provides an overloaded version, which provides a Boolean parameter to reverse this behavior and take them into account to a lesser extent.

7. Use Guava

Now, let's look at Guava. Guava 是 Google 的1个开源项目,包含许多 Google 核心 Java 常用库,如:集合 [collections] 、缓存 [caching] 、原生类型支持 [primitives support] 、并发库 [concurrency libraries] 、通用注解 [common annotations] 、字符串处理 [string processing] 与 I/O 等。

First, let's import the dependencies:


<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>29.0-jre</version>
</dependency>

7.1 equal Method for Objects

Similar to the apache commons library, Google gives us a way to determine whether two objects are equal, objects # equal. Although they have different implementations, they return the same result:


String a = new String("Hello!");
String b = new String("Hello!");

assertThat(Objects.equal(a, b)).isTrue();

Although it is not labeled deprecated, the JavaDoc for this method says it should be considered deprecated because java7 provides the Objects # equals method.

7.2 Comparison Method

For now, the Guava library does not provide a way to compare two objects (we'll see how to do this in the next section), but it does provide a way to compare the original values. Let's look at how the compare () method of the Ints helper class works:


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

Typically, if the first parameter is less than, equal to, or greater than the second parameter, it returns an integer that may be negative, zero, or positive. All primitive types except bytes have similar methods.

7.3 ComparisonChain Class

Finally, the Guava library provides the ComparisonChain class, which allows us to compare two objects through a series of comparisons. We can easily compare the first and last names of two people:


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 implemented using the compareTo () method, so the parameters passed to the compare () method must be primitive types or comparable objects.

8. Complete code

Java Object Comparison


Related articles: