Java各种比较对象的方式的对比总结

一、==和!=操作符

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

1.1 原始类型(Primitives)

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

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

感谢自动拆箱,在将原语值与其包装类型对应值进行比较时,也可以这样做:

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

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

1.2 对象比较

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

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

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

通过比较两个对象,这些对象的值不是1,而是它们在堆栈中的内存地址,因为这两个对象都是使用new操作符创建的。如果我们把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()方法将整数存储在缓存中,以避免创建太多具有相同值的包装器对象。因此,该方法为两个调用返回相同的整数实例。

对字符串也是一样:

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

但是,如果它们是使用new操作符创建的,那么它们就不一样了。最后,两个空引用被认为是相同的,而任何非空对象将被认为与空对象不同:

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

当然,相等运算符的行为可能是有限的。如果我们想比较两个映射到不同地址的对象,并根据它们的内部状态将它们视为相等,那该怎么办?我们将在下一节中看到如何操作。

二、Object的equals方法

现在,让我们用equals()方法讨论一个更广泛的相等概念。这个方法是在Object类中定义的,以便每个Java对象都继承它。默认情况下,它的实现比较对象内存地址,因此它的工作方式与==运算符相同。但是,我们可以重写这个方法来定义相等对我们的对象意味着什么。

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

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

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

当两个对象相同时,该方法仍然返回true。我们应该注意,我们可以传递一个空对象作为方法的参数,但是当然,不能作为调用方法的对象。我们可以对自己的对象使用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类中,我们希望按Person对象的姓氏进行比较:

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

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

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

五、Comparator接口

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

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

假设我们有一个在网页中有一个展示人信息的表格,我们想让用户能够按名字而不是姓氏对他们进行排序。如果我们还想保持当前的实现,那么使用Comparable是不可能的,但是我们可以实现自己的比较器。

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

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

现在让我们用这个比较器对一组人进行排序:

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

在compareTo()实现中,可以使用Comparator接口上的其他方法:

@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.11</version>
</dependency>

6.1 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之后,这种方法就被弃用了。

6.2 ObjectUtils的compare方法

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

让我们再看看如何使用字符串:

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

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

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

七、使用Guava

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

首先,让我们导入依赖项:

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

7.1 Objects的equal方法

与apache commons库类似,Google为我们提供了一种方法来确定两个对象是否相等,objects#equal。尽管它们有不同的实现,但它们返回相同的结果:

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

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

尽管它没有被标记为deprecated,但是这个方法的JavaDoc说它应该被视为deprecated,因为java7提供了Objects#equals方法。

7.2 Comparison方法

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

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

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

7.3 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对象比较

到此这篇关于Java各种比较对象的方式的对比总结的文章就介绍到这了,更多相关Java比较对象的方式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于java中两个对象属性的比较

    两个对象进行比较相等,有两种做法: 1.情况一:当仅仅只是判断两个对象是否相等时,只需重写equals()方法即可.这里就不用说明 2.情况二:当除了情况一之外,还需知道是那个属性不同,那么就需要采用类反射, 具体代码如下: public static void main(String[] args) { A a = new A(); a.setUserName("a"); a.setPassword("p"); a.setQq("q"); a.

  • java使用compareTo实现一个类的对象之间比较大小操作

    首先定义一个对象,注意这个对象必须实现Comparable接口,并重写这个接口的compareTo方法 package cn.demo; public class Student implements Comparable{ private int number=0; //学号 private String name=""; //学生姓名 private String gender=""; //性别 public int getNumber(){ return nu

  • Java比较对象大小两种常用方法

    引入原因: Java中的对象,正常情况下,只能进行比较:== 或!= ,不能使用 < 或 > ,但是在开发时需要用到比较对象的大小 1.Comparable接口的使用(自然排序) 1.像String .包装类等实现了Comparable接口,重写了compareTo()方法,给出了比较两个对象大小的方法 2.像String .包装类等重写了compareTo()方法后,默认执行了从小到大的排序 3.重写compareTo()的规则: 如果当前对象this大于形参对象obj,则返回正整数,如果当

  • Java中实现Comparable和Comparator对象比较

    当需要排序的集合或数组不是单纯的数字型时,通常可以使用Comparator或Comparable,以简单的方式实现对象排序或自定义排序. A comparison function, which imposes a total ordering on some collection of objects. Comparators can be passed to a sort method (such as Collections.sort or Arrays.sort) to allow pr

  • Java中对象的比较操作实例分析

    本文实例讲述了Java中对象的比较操作.分享给大家供大家参考,具体如下: 一 点睛 在Java中,有两种方式可用于对象间的比较: 利用"=="运算符:用于比较两个对象的内存地址值(引用值)是否相等. 利用equals()方法:用于比较两个对象的内容是否一致. 二 "=="运算符的比较 1 代码 public class CompareObject1 { public static void main( String[] args ) { String str1 =

  • Java各种比较对象的方式的对比总结

    一.==和!=操作符 让我们从==和!=开始可以分别判断两个Java对象是否相同的操作符. 1.1 原始类型(Primitives) 对于原始类型,相同意味着具有相等的值: assertThat(1 == 1).isTrue(); 感谢自动拆箱,在将原语值与其包装类型对应值进行比较时,也可以这样做: Integer a = new Integer(1); assertThat(1 == a).isTrue(); 如果两个整数的值不同,==运算符将返回false,而!=运算符将返回true. 1.

  • Java中实现线程的三种方式及对比_动力节点Java学院整理

    Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行体. (2)创建Thread子类的实例,即创建了线程对象. (3)调用线程对象的start()方法来启动该线程. package com.thread; public class FirstThreadTest extends Thread{ int i = 0; //重写run方法,run方法的方

  • java中同类对象之间的compareTo()和compare()方法对比分析

    首先我们都知道java中的比较都是同一类对象与对象之间的比较,就好像现实生活中比较人和人的年龄一样,你不会去把人的年龄和人的身高来比较,这显然是没有意义的. java中同类对象之间的比较又分为两种,基本类型之间的比较和引用类型之间的比较. java中"=="比较对象是否引用了同一个对象,或者比较基本类型变量值是否相等.Object类的equals()方法用来比较是否一个对象(内存地址比较),可以重写. JDK中有些类重写了equals()方法,只要类型.内容都相同,就认为相等.很变态的

  • 关于Java创建线程的2种方式以及对比

    目录 1. 继承Thread类 2. 实现Runnable接口: 创建线程的两种方式对比: 线程的完整生命周期: 总结 Java中两种创建线程的方式: 1. 继承Thread类 重写run()方法 new一个线程对象 调用对象的start()启动线程 class Handler extends Thread{ public void run(){ //重写run()方法 } public static void main(String[] args){ Thread thread=new Han

  • Java Kryo,Protostuff,Hessian序列化方式对比

    目录 前言 性能对比 前期准备 序列化后字节所占空间大小比较 其他比较 小结 前言 前段时间在写RPC框架的时候用到了Kryo.Hessian.Protostuff三种序列化方式.但是当时因为急于实现功能,就只是简单的的看了一下如何使用这三种序列化方式,并没有去深入研究各自的特性,以及优点和缺点.知道现在就将RPC框架写完了之后,才有时间静下心来对三种方式做一个对比,总结. Kryo.Hessain.Protostuff都是第三方开源的序列化/反序列化框架,要了解其各自的特性,我们首先需要知道序

  • Java中线程Thread的三种方式和对比

    介绍 多线程主要的作用就是充分利用cpu的资源.单线程处理,在文件的加载的过程中,处理器就会一直处于空闲,但也被加入到总执行时间之内,串行执行切分总时间,等于每切分一个时间*切分后字符串的个数,执行程序,估计等几分钟能处理完就不错了.而多线程处理,文件加载与差分过程中 一.Java实现多线程的三种方式 1.继承Thread 通过Thread继承,并重写run方法来实现多线程,案例如下: public class ThreadPattern extends Thread { @Override p

  • Java实现储存对象并按对象某属性排序的几种方法示例

    本文实例讲述了Java实现储存对象并按对象某属性排序的几种方法.分享给大家供大家参考,具体如下: 在编程的时候,经常会出现对某一种类的对象们按照某属性进行自定义的排序,比如:学生对象按照age大小排序. 有一种方法就是把age单独提出来排好序,然后按照ages数组的顺序把students重存一次.但是这样太繁琐了,有没有更好的方法呢? 有滴~ 第一种,可以实现边添加边排序,需要用到TreeSet. 第二种,用数组存放对象们,但是不需单独取出某属性排列好再重存,而是在原数组上用比较器重新排一次序.

  • Java常见异常及处理方式总结

    一.概述 异常指不期而至的各种状况,它在程序运行的过程中发生.作为开发者,我们都希望自己写的代码 永远都不会出现 bug,然而现实告诉我们并没有这样的情景.如果用户在程序的使用过程中因为一些原因造成他的数据丢失,这个用户就可能不会再使用该程序了.所以,对于程序的错误以及外部环境能够对用户造成的影响,我们应当及时报告并且以适当的方式来处理这个错误. 之所以要处理异常,也是为了增强程序的鲁棒性. 异常都是从 Throwable 类派生出来的,而 Throwable 类是直接从 Object 类继承而

  • Java虚拟机之对象创建过程与类加载机制及双亲委派模型

    目录 一.对象的创建过程: 1.对象的创建过程: 2.对象的访问方式: 二.类加载机制: 2.1.加载阶段: 2.2.验证阶段: 2.3.准备阶段: 2.4.解析阶段: 2.5.初始化: 2.5.1.类的主动引用: 2.5.2.类的被动引用: 2.5.3.()方法的特点: 三.类加载器与双亲委派模型: 3.1.JVM 的类加载器: 3.2.双亲委派模型: 3.2.1.双亲委派模型的工作原理: 3.2.2.双亲委派模型的优点: 3.3.类加载器源码:loadClass() 3.4.如何破坏双亲委派

  • 一文了解Java中record和lombok的使用对比

    目录 小型不可变对象 透明的数据载体 有许多字段的类 可变数据 继承性 结论 大家好,我是指北君. Java的 record 关键字是Java 14中引入的一个新的语义特性.record 对于创建小型不可变的对象非常有用.另一方面,Lombok 是一个Java库,可以自动生成一些已知的模式为Java字节码.尽管它们都可以用来减少模板代码,但它们是不同的工具.因此,我们应该在特定情况下使用更适合我们需求的工具. 在这篇文章中,我们将探讨各种使用情况,包括java record 的一些限制.对于每个

随机推荐