Comparing Objects in Java – 在Java中比较对象

最后修改: 2020年 5月 22日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

Comparing objects is an essential feature of object-oriented programming languages.

比较对象是面向对象编程语言的一个基本特征。

In this tutorial, we’ll explore some of the features of the Java language that allow us to compare objects. We’ll also look at such features in external libraries.

在本教程中,我们将探讨Java语言中允许我们比较对象的一些特性。我们还将研究外部库中的此类功能。

2. == and != Operators

2.==!=操作符

Let’s begin with the == and != operators, which can tell if two Java objects are the same or not, respectively.

让我们从==!=操作符开始,它们可以分别判断两个Java对象是否相同。

2.1. Primitives

2.1.基本原理

For primitive types, being the same means having equal values:

对于原始类型,相同意味着具有相等的值:

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

Thanks to auto-unboxing, this also works when comparing a primitive value with its wrapper type counterpart:

由于自动拆箱,在比较一个原始值和它的包装类型对应物时,这也是有效的

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

If two integers have different values, the == operator will return false, while the != operator will return true.

如果两个整数的值不同,==操作符将返回false,而!=操作符将返回true

2.2. Objects

2.2 对象

Let’s say we want to compare two Integer wrapper types with the same value:

假设我们想比较两个具有相同值的Integer封装器类型。

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

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

By comparing two objects, the value of those objects isn’t 1. Rather, it’s their memory addresses in the stack that are different, since both objects are created using the new operator. If we assigned a to b, then we would have a different result:

通过比较两个对象,这些对象的值不是1,而是它们在堆栈中的内存地址不同,因为两个对象都是使用new操作符创建的。如果我们把a分配给b,那么我们会有不同的结果。

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#valueOffactory方法时会发生什么。

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

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

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

在这种情况下,它们被认为是相同的。这是因为valueOf()方法将Integer存储在一个缓存中,以避免创建过多具有相同值的封装对象。因此,该方法在两次调用中返回相同的Integer实例。

Java also does this for String:

Java对String也是如此。

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

However, if they’re created using the new operator, then they won’t be the same.

然而,如果它们是用new操作符创建的,那么它们就不一样了。

Finally, two null references are considered the same, while any non-null object is considered different from null:

最后,两个null引用被认为是相同的,而任何非null对象被认为与null不同。

assertThat(null == null).isTrue();

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

Of course, the behavior of the equality operators can be limiting. What if we want to compare two objects mapped to different addresses and yet have them considered equal based on their internal states? We’ll see how to do this in the next sections.

当然,平等运算符的行为可能是限制性的。如果我们想比较两个映射到不同地址的对象,但根据它们的内部状态认为它们是相等的,该怎么办?我们将在接下来的章节中看到如何做到这一点。

3. Object#equals Method

3.Object#equals方法

Now let’s talk about a broader concept of equality with the equals() method.

现在让我们用equals()方法谈谈更广泛的平等概念。

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 as the == operator. However, we can override this method in order to define what equality means for our objects.

这个方法被定义在Object类中,因此每个Java对象都会继承它。默认情况下,它的实现是比较对象的内存地址,所以它的作用与==操作符相同。然而,我们可以重写这个方法,以便定义平等对我们的对象意味着什么。

First, let’s see how it behaves for existing objects like Integer:

首先,让我们看看它对现有对象如Integer的表现。

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

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

The method still returns true when both objects are the same.

当两个对象相同时,该方法仍然返回true

We should note that we can pass a null object as the argument of the method, but not as the object we call the method upon.

我们应该注意,我们可以传递一个null对象作为方法的参数,但不能作为我们调用方法的对象。

We can also use the equals() method with an object of our own. Let’s say we have a Person class:

我们也可以用我们自己的对象来使用equals()方法。比方说,我们有一个Person类。

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 equals() method for this class so that we can compare two Persons based on their internal details:

我们可以覆盖这个类的equals()方法,这样我们就可以根据两个Person的内部细节来比较。

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

For more information, check out our article about this topic.

更多信息,请查看我们的关于此主题的文章

4. Objects#equals Static Method

4.Objects#equals 静态方法

Now let’s look at the Objects#equals static method. We mentioned earlier that we can’t use null as the value of the first object, otherwise a NullPointerException will be thrown.

现在我们来看看Objects#equals静态方法。我们之前提到,我们不能使用null作为第一个对象的值,否则将抛出NullPointerException

The equals() method of the Objects helper class solves that problem. It takes two arguments and compares them, also handling null values.

Objects辅助类的equals()方法解决了这个问题。它接收两个参数并进行比较,同时也处理null值。

Let’s compare Person objects again:

让我们再次比较Person对象。

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();

As we explained, this method handles null values. Therefore, if both arguments are null, it’ll return true, and if only one of them is null, it’ll return false.

正如我们所解释的,这个方法处理null值。因此,如果两个参数都是null,它将返回true,如果只有一个参数是null,它将返回false

This can be really handy. Let’s say we want to add an optional birth date to our Person class:

这可能真的很方便。比方说,我们想给我们的Person类添加一个可选的出生日期。

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

Then we have to update our equals() method, but with null handling. We can do this by adding the condition to our equals() method:

然后我们必须更新我们的equals()方法,但是要对null进行处理。我们可以通过在equals()方法中添加条件来做到这一点。

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

However, if we add too many nullable fields to our class, it can become really messy. Using the Objects#equals method in our equals() implementation is much cleaner, and improves readability:

然而,如果我们在我们的类中添加了太多的nullable字段,它就会变得非常混乱。在我们的equals()实现中使用Objects#equals方法要干净得多,并且提高了可读性。

Objects.equals(birthDate, that.birthDate);

5. Comparable Interface

5.可比的接口

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.

比较逻辑也可用于将对象置于特定的顺序中。Comparable接口允许我们通过确定一个对象是否大于、等于或小于另一个对象来定义对象之间的排序

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’re equal, and positive otherwise.

Comparable接口是通用的,只有一个方法,compareTo(),它接收一个通用类型的参数并返回一个int。如果this比参数低,返回的值是负数,如果它们相等,返回的值是0,否则就是正数。

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

比方说,在我们的Person类中,我们想通过姓氏来比较Person对象。

public class Person implements Comparable<Person> {
    //...

    @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.

compareTo()方法将返回一个负的int,如果被调用的Person的姓氏大于this,如果姓氏相同则为零,否则为正。

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

更多信息,请看我们的关于这个主题的文章

6. Comparator Interface

6.比较器接口

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 this pattern earlier with the Comparable interface.

Comparator接口是通用的,并且有一个compare方法,该方法接收该通用类型的两个参数并返回一个integer。我们已经在前面的Comparable接口中看到了这种模式。

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

Comparator也是类似的;但是,它与类的定义是分开的。因此,我们可以为一个类定义任意多的Comparator,而我们只能提供一个Comparable实现。

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. This isn’t possible with Comparable if we also want to keep our current implementation, but we can implement our own Comparators.

让我们想象一下,我们有一个在表格视图中显示人的网页,我们想为用户提供按名字而不是姓氏排序的可能性。如果我们还想保留我们当前的实现,这在Comparable中是不可能的,但是我们可以实现我们自己的Comparators

Let’s create a Person Comparator that will compare them only by their first names:

让我们创建一个Person Comparator,它将只通过他们的名字进行比较。

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

Now let’s sort a List of people using that Comparator:

现在让我们使用该比较器对一个列表进行排序。

Person joe = new Person("Joe", "Portman");
Person allan = new Person("Allan", "Dale");

List<Person> people = new ArrayList<>();
people.add(joe);
people.add(allan);

people.sort(compareByFirstNames);

assertThat(people).containsExactly(allan, joe);

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

Comparator接口上还有其他方法,我们可以在compareTo() 实现中使用。

@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’re first comparing last names, then first names. Next we compare birth dates, but as they’re nullable, we must say how to handle that. To do this, we give a second argument to say that they should be compared according to their natural order, with null values going last.

在这种情况下,我们首先要比较姓,然后是名。接下来我们要比较出生日期,但由于它们是可忽略的,我们必须说如何处理。为了做到这一点,我们给出第二个参数,说它们应该按照自然顺序进行比较,空值最后。

7. Apache Commons

7.阿帕奇公社

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

让我们来看看Apache Commons库。首先,让我们导入Maven依赖项

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

7.1. ObjectUtils#notEqual Method

7.1.ObjectUtils#notEqual方法

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

首先,我们来谈谈ObjectUtils#notEqual方法。它需要两个Object 参数来确定它们是否不相等,根据它们自己的equals()方法实现。它还可以处理null值。

Let’s reuse our String examples:

让我们重新使用我们的String例子。

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

应该注意的是,ObjectUtils有一个equals()方法。然而,自从Java 7出现Objects#equals之后,这个方法已经被废弃了。

7.2. ObjectUtils#compare Method

7.2.ObjectUtils#compare 方法

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.

现在让我们用ObjectUtils#compare方法来比较对象顺序。这是一个通用方法,它接收两个该通用类型的Comparable参数并返回一个Integer

Let’s see it using Strings again:

让我们再用Strings看看。

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 greater. It also offers an overloaded version that offers to invert that behavior and consider them lesser, taking a boolean argument.

默认情况下,该方法处理null值的方式是将其视为较大。它还提供了一个重载版本,提供了反转该行为并将其视为较小的,接受一个boolean参数。

8. Guava

8.番石榴

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

让我们看一下Guava。首先,让我们导入依赖关系

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

8.1. Objects#equal Method

8.1.对象#equal方法

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:

与Apache Commons库类似,Google为我们提供了一个确定两个对象是否相等的方法,Objects#equal。虽然它们有不同的实现,但它们返回的结果是一样的:

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 for this method says that it should be considered as deprecated, since Java 7 provides the Objects#equals method.

虽然它没有被标记为废弃,但这个方法的JavaDoc说,它应该被视为废弃,因为Java 7提供了Objects#equals方法。

8.2. Comparison Methods

8.2.比较方法

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:

Guava库没有提供比较两个对象的方法(我们将在下一节中看到我们可以实现这个目标),但它确实为我们提供了比较原始值的方法。让我们使用Ints辅助类,看看它的compare()方法是如何工作的。

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.

像往常一样,它返回一个整数,如果第一个参数小于、等于或大于第二个参数,它可能是负的、零的或正的。除了bytes之外,所有的原始类型都有类似的方法。

8.3. ComparisonChain Class

8.3.比较链

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:

最后,Guava库提供了ComparisonChain类,允许我们通过一连串的比较来比较两个对象。我们可以轻松地通过名字和姓氏来比较两个Person对象。

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.

底层比较是通过compareTo()方法实现的,所以传递给compare()方法的参数必须是基元或Comparables。

9. Conclusion

9.结语

In this article, we learned different ways to compare objects in Java. We examined the difference between sameness, equality, and ordering. We also looked at the corresponding features in the Apache Commons and Guava libraries.

在这篇文章中,我们学习了在Java中比较对象的不同方法。我们研究了同一性、平等性和排序之间的区别。我们还看了Apache Commons和Guava库中的相应功能。

As usual, the full code for this article can be found over on GitHub.

像往常一样,本文的完整代码可以在GitHub上找到超过