3年前 (2021-10-23)  Java系列 |   抢沙发  3215 
文章评分 0 次,平均分 0.0
[收起] 文章目录

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

在本文中,我们将了解Java语言的一些特性,这些特性允许我们比较对象。此外,我们还将研究外部库中的这些特性。

==  还有!= 运算符

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

原语

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

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

由于自动取消装箱,在将原语值与其包装类型对应项进行比较时,此功能也会起作用:

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

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

Objects

假设我们要比较两种具有相同值的整数包装类型:

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

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

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

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

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

现在,让我们看看使用Integer#valueOf factory方法时会发生什么:

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

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

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

Java也对字符串执行此操作:

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

但是,如果它们是使用new操作符创建的,则它们将不同。

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

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

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

当然,相等运算符的行为可能会受到限制。如果我们想要比较映射到不同地址的两个对象,并且根据它们的内部状态将它们视为相等,该怎么办?我们将在下一节中了解如何进行。

Object#equals Method

现在,让我们用equals()方法讨论一个更广泛的相等概念。

此方法在对象类中定义,以便每个Java对象都继承它。默认情况下,它的实现比较对象内存地址,因此其工作原理与==运算符相同。但是,我们可以重写此方法以定义相等对对象的意义。

首先,让我们看看它对现有对象(如Integer)的行为:

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

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

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

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

我们可以对自己的对象使用equals()方法。假设我们有一个Person类:

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

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

我们可以重写该类的equals()方法,以便根据两个人的内部详细信息比较他们:

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

Objects#equals静态方法

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

Objects助手类的equals()方法解决了这个问题。它接受两个参数并对它们进行比较,还处理空值。

让我们再次将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();

如前所述,该方法处理空值。因此,如果两个参数都为null,则返回true;如果其中只有一个参数为null,则返回false

这真的很方便。假设我们想在Person类中添加一个可选的出生日期:

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

然后,我们必须更新equals()方法,但要使用null处理。我们可以通过将此条件添加到equals()方法来实现:

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

然而,如果我们向类中添加许多可为null的字段,它可能会变得非常混乱。在equals()实现中使用Objects#equals方法更加简洁,并提高了可读性:

Objects.equals(birthDate, that.birthDate);

Comparable接口

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

Compariable接口是泛型接口,只有一个方法compareTo(),该方法接受泛型类型的参数并返回int。如果该参数小于该参数,则返回负值;如果它们相等,则返回0;否则返回正值。

比如说,在我们的Person类中,我们希望通过name比较Person对象:

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

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

如果使用name大于此值的人调用compareTo()方法,则返回负int;如果姓氏相同,则返回零;否则返回正int

Comparator接口

Comparator接口是泛型的,并且有一个compare方法,该方法接受该泛型类型的两个参数并返回一个整数。我们在前面已经看到了类似接口的模式。

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

让我们想象一下,我们有一个网页,在一个表视图中显示人们,我们想让用户能够按名字而不是姓氏对他们进行排序。如果我们还想保持当前的实现,那么使用Comparable是不可能的,但是我们可以实现自己的Comparator

让我们创建一个Person Comparator,它将只根据他们的名字进行比较:

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

现在,让我们使用该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);

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

在本例中,我们首先比较姓,然后比较名。然后,我们比较出生日期,但由于它们可以为空,我们必须说明如何处理它,所以我们给出了第二个参数,告诉我们应该根据它们的自然顺序对它们进行比较,但空值最后会出现。

Apache Commons

现在让我们看看ApacheCommons库。首先,让我们导入Maven依赖项:

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

ObjectUtils#notEqual方法

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

让我们重新使用字符串示例:

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

assertThat(ObjectUtils.notEqual(a, b)).isTrue();

应该注意,ObjectUtils有一个equals()方法。然而,自从Java7出现Objects#equals之后,这种方法就被弃用了

ObjectUtils#compare方法

现在,让我们用ObjectUtils#compare方法比较对象顺序。它是一个泛型方法,接受该泛型类型的两个可比较参数并返回一个整数。

让我们看看再次使用字符串:

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

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

默认情况下,该方法通过将空值视为更大值来处理空值。它提供了一个重载版本,用于反转该行为,并考虑较小的布尔值。

Guava

现在,让我们来看看Guava。首先,让我们导入依赖项:

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

Objects#equal方法

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

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

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

虽然它没有被标记为不推荐使用,但该方法的JavaDoc表示,由于Java7提供了Objects#equals方法,因此应该将其视为不推荐使用。

Comparison方法

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

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

通常,如果第一个参数分别小于、等于或大于第二个参数,则返回一个整数,该整数可能为负、零或正。除字节外,所有基元类型都有类似的方法。

ComparisonChain类

最后,Guava库提供了ComparisonChain类,允许我们通过一系列比较来比较两个对象。我们可以通过名字和姓氏轻松比较两个人对象:

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

底层比较是使用compareTo()方法实现的,因此传递给compare()方法的参数必须是原语或可比较的。

结论

在本文中,我们研究了在Java中比较对象的不同方法。我们研究了相同性、平等性和有序性之间的差异。我们还查看了ApacheCommons和Guava库中的相应功能。

与往常一样,本文的完整代码可以在GitHub上找到:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-lang-2

  
 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2471.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册