当前位置: 首页 > news >正文

【Java SE】对象的比较(==、equals()、Comparab和Comparator)

对象的比较

  • `==` 与 `equals()`
    • `==` 到底在比什么?
    • `equals()`
  • 重写 `equals()` 时必须重写 `hashCode()`
  • 顺序比较:`Comparable` 与 `Comparator`
    • 内排序:`Comparable` 接口
    • 外排序:`Comparator` 接口
  • 总结 ⭐
  • 面试笔试题精选
    • 包装类 、 == 和equals()
    • hashCode()
    • 补全equals 和 hashCode 方法
    • Comparable 和 Comparator 的区别
    • Comparableh和Comparator 代码输出问题
    • 电商系统订单排序
    • 以下代码存在什么问题?

==equals()

==到底在比什么?

==是Java中的一个运算符,其比较规则非常直接:

  • 基本数据类型(如 int, double, char):比较的是数值是否相等。
  • 引用数据类型(如 String, Object, 自定义类):比较的是内存地址,即判断两个引用是否指向堆内存中的同一个对象
inta=10;intb=10;System.out.println(a==b);// true,比较值Strings1=newString("hello");Strings2=newString("hello");System.out.println(s1==s2);// false,两个不同的对象,内存地址不同Strings3="world";Strings4="world";System.out.println(s3==s4);// true,字符串常量池复用,指向同一对象

equals()

equals()Object类的一个方法。在Object类中,它的默认实现就是使用==,即比较引用地址。

// Object 类中的默认实现publicbooleanequals(Objectobj){return(this==obj);}

因此,如果在自定义类中不重写equals()方法,那么obj1.equals(obj2)obj1 == obj2没有任何区别。

真正的价值在于重写equals()。比较两个不同的对象时,根据对象内部的属性值来判断它们是否“逻辑上相等”。这正是String类所做的。

Strings1=newString("hello");Strings2=newString("hello");System.out.println(s1.equals(s2));// true,String重写了equals,比较的是字符序列

重写equals()时必须重写hashCode()

先看一个错误的示例:

importjava.util.HashMap;classPerson{Stringname;intage;Person(Stringname,intage){this.name=name;this.age=age;}// 只重写equals,不重写hashCodepublicbooleanequals(Objectobj){if(this==obj)returntrue;if(obj==null||getClass()!=obj.getClass())returnfalse;Personperson=(Person)obj;returnage==person.age&&name.equals(person.name);}}publicclassTest{publicstaticvoidmain(String[]args){HashMap<Person,String>map=newHashMap<>();Personp1=newPerson("Alice",20);Personp2=newPerson("Alice",20);map.put(p1,"Engineer");System.out.println(map.get(p2));// 输出 null! 而不是 "Engineer"}}

为什么会输出null

  1. p1p2根据equals判断是相等的。
  2. HashMap中,先通过hashCode()找到对象存储的“桶”位置。
  3. 由于没有重写hashCode()p1p2的哈希码大概率不同,它们被放到了不同的桶里。
  4. p2去查找时,在p2的哈希码对应的桶里找不到p1,因此返回null

约定与标准重写方式

  • 一致性:如果两个对象通过equals()比较相等,那么它们的hashCode()必须相等。
  • 非逆反:如果两个对象的hashCode()相等,它们的equals()不一定相等(哈希冲突)。

标准重写模板(以Person类为例,使用Objects工具类):

importjava.util.Objects;classPerson{Stringname;intage;// ... 构造器、getter/setter ...@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(o==null||getClass()!=o.getClass())returnfalse;Personperson=(Person)o;returnage==person.age&&Objects.equals(name,person.name);}@OverridepublicinthashCode(){returnObjects.hash(name,age);// 使用相同的关键字段计算哈希码}}

提示:现代IDE(如IntelliJ IDEA、Eclipse)都可以一键自动生成equals()hashCode(),非常方便,推荐使用。

顺序比较:ComparableComparator

除了判断相等,经常需要对对象进行排序(如Collections.sort(list))。这时就需要定义对象之间的自然顺序。

内排序:Comparable接口

Comparable被称为内部比较器。一个类实现了Comparable接口,就意味着它自身具备可比较的能力

publicinterfaceComparable<T>{publicintcompareTo(To);}
  • 返回负数:当前对象 < 参数对象
  • 返回0:当前对象 == 参数对象
  • 返回正数:当前对象 > 参数对象

示例:让 Person 按年龄升序排序

classPersonimplementsComparable<Person>{Stringname;intage;// ... 构造器 ...@OverridepublicintcompareTo(Persono){returnthis.age-o.age;// 按年龄升序// 注意:避免整数溢出,更稳妥写法:return Integer.compare(this.age, o.age);}}// 使用List<Person>list=newArrayList<>();Collections.sort(list);// 直接排序,因为Person实现了Comparable

优点:一旦定义,排序规则统一。
缺点:只能有一种排序规则,且侵入到类内部。

外排序:Comparator接口

Comparator被称为外部比较器。它不需要修改原始类的代码,可以灵活地定义多种比较策略。

publicinterfaceComparator<T>{intcompare(To1,To2);}

示例:多种排序方式

// 1. 按姓名升序Comparator<Person>nameComparator=newComparator<Person>(){@Overridepublicintcompare(Personp1,Personp2){returnp1.name.compareTo(p2.name);}};// 2. 按年龄降序(Lambda表达式更简洁)Comparator<Person>ageDescComparator=(p1,p2)->p2.age-p1.age;// 3. 链式调用:先按姓名,再按年龄Comparator<Person>complexComparator=Comparator.comparing(Person::getName).thenComparing(Person::getAge);// 使用Collections.sort(list,nameComparator);// 或 list.sort(ageDescComparator);

优点:灵活、解耦、支持链式调用和组合。
缺点:代码相对多一点(但Lambda大大简化了)。

总结 ⭐

特性==equals()ComparableComparator
类型运算符Object的方法接口(java.lang)接口(java.util)
用途比较基本类型值或引用地址比较对象逻辑相等性定义对象的自然顺序定义自定义顺序
需要重写是(对于自定义类)是(定义匿名/独立实现)
侵入性有(修改原类)
常用场景基本类型比较、判断是否同一对象集合中查找、去重(配合hashCode)Collections.sort(list)Collections.sort(list, comp)、多字段排序

面试笔试题精选

包装类 、 == 和equals()

Integera=127;Integerb=127;System.out.println(a==b);System.out.println(a.equals(b));Integerc=128;Integerd=128;System.out.println(c==d);System.out.println(c.equals(d));

A. true, true, false, true
B. true, true, true, true
C. true, false, false, true
D. false, true, false, true

答案:A
解析:(valueOf() 源码分析部分)

hashCode()

以下关于 hashCode() 的说法,正确的是?

A. 两个对象相等,hashCode 必须相等
B. 两个对象 hashCode 相等,则对象一定相等
C. 重写 equals 时可以不重写 hashCode
D. hashCode 相同的对象一定放在同一个哈希桶中

答案:A
解析:

  • A 正确:这是 equals 和 hashCode 的核心约定。
  • B 错误:哈希冲突时,不同对象可能 hashCode 相同。
  • C 错误:违反约定,会导致 HashMap、HashSet 等集合出现异常行为。
  • D 错误:hashCode 决定桶位置,但相同桶内可能有多个不同对象(链表/红黑树)。

补全equals 和 hashCode 方法

补全以下代码,使 Person 对象能正确在 HashSet 中去重

importjava.util.Objects;importjava.util.HashSet;classPerson{Stringname;intage;Person(Stringname,intage){this.name=name;this.age=age;}// 请补全 equals 和 hashCode 方法// 要求:name 和 age 都相等时视为同一个对象// 你的代码写在这里 ↓// 你的代码写在这里 ↑}publicclassTest{publicstaticvoidmain(String[]args){HashSet<Person>set=newHashSet<>();set.add(newPerson("张三",20));set.add(newPerson("张三",20));set.add(newPerson("李四",22));System.out.println(set.size());// 期望输出 2}}
参考答案
@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(o==null||getClass()!=o.getClass())returnfalse;Personperson=(Person)o;returnage==person.age&&Objects.equals(name,person.name);}@OverridepublicinthashCode(){returnObjects.hash(name,age);}

Comparable 和 Comparator 的区别

参考答案
维度ComparableComparator
位置java.lang 包java.util 包
方法compareTo(T o)compare(T o1, T o2)
侵入性侵入实体类,修改原类不侵入,独立实现
排序规则单一(自然顺序)多种(可灵活定义)
使用场景类设计时就确定默认排序临时指定排序、多字段排序
Lambda支持支持,写法简洁

典型使用:

  • 想让某个类默认能排序 →implements Comparable
  • 想对集合按不同规则排序 → 传入Comparator

Comparableh和Comparator 代码输出问题

importjava.util.*;classStudentimplementsComparable<Student>{Stringname;intscore;Student(Stringname,intscore){this.name=name;this.score=score;}@OverridepublicintcompareTo(Studento){returnthis.score-o.score;}@OverridepublicStringtoString(){returnname+":"+score;}}publicclassMain{publicstaticvoidmain(String[]args){List<Student>list=newArrayList<>();list.add(newStudent("Alice",85));list.add(newStudent("Bob",75));list.add(newStudent("Charlie",95));Collections.sort(list);System.out.println(list);// 额外:按名字降序排序list.sort((s1,s2)->s2.name.compareTo(s1.name));System.out.println(list);}}

输出:

[Bob:75, Alice:85, Charlie:95] [Charlie:95, Bob:75, Alice:85]

解析:

  • 第一次排序:使用Comparable,按score升序 → 75, 85, 95
  • 第二次排序:使用ComparatorLambda,按name降序 → Charlie, Bob, Alice

电商系统订单排序

某电商系统需要对订单进行排序,规则如下:

  • 优先按订单状态排序(待支付 < 已支付 < 已发货 < 已完成)
  • 状态相同时,按创建时间倒序(最新的在前)
  • 时间也相同时,按订单金额降序

请使用Comparator链式调用来实现。

classOrder{Stringstatus;// "待支付", "已支付", "已发货", "已完成"LocalDateTimecreateTime;BigDecimalamount;// getter/setter 省略}// 请实现一个 ComparatorComparator<Order>orderComparator=?
参考答案
// 先定义状态优先级映射Map<String,Integer>statusPriority=Map.of("待支付",1,"已支付",2,"已发货",3,"已完成",4);Comparator<Order>orderComparator=Comparator.comparingInt(o->statusPriority.get(o.getStatus())).thenComparing(Order::getCreateTime,Comparator.reverseOrder()).thenComparing(Order::getAmount,Comparator.reverseOrder());

要点:

  • 使用Comparator.comparingInt处理枚举状态
  • Comparator.reverseOrder()实现倒序
  • 链式调用清晰表达多级排序逻辑

以下代码存在什么问题?

classPoint{intx,y;Point(intx,inty){this.x=x;this.y=y;}@Overridepublicbooleanequals(Objectobj){Pointother=(Point)obj;returnthis.x==other.x&&this.y==other.y;}}
答案及解析

问题:

  1. 缺少 null 判断:如果传入 null,强制类型转换会抛出NullPointerException
  2. 缺少类型判断:如果传入的不是 Point 类型,强制转换会抛出ClassCastException
  3. 缺少hashCode()重写:违反约定,放入 HashSet/HashMap 会有问题
  4. 缺少this == obj优化:缺少身份比较的性能优化

修正后:

@Overridepublicbooleanequals(Objectobj){if(this==obj)returntrue;if(obj==null||getClass()!=obj.getClass())returnfalse;Pointpoint=(Point)obj;returnx==point.x&&y==point.y;}@OverridepublicinthashCode(){returnObjects.hash(x,y);}

补充:字符串的比较

http://www.jsqmd.com/news/603874/

相关文章:

  • 告别染色差异焦虑:5分钟用pip安装wsi-normalizer,批量处理你的病理切片Patch
  • Halcon图片拼接避坑指南:特征点匹配常见问题与解决方案
  • 别再只会用*号了!手把手教你用Verilog实现4位乘法器(附Modelsim仿真与Vivado综合结果)
  • 进程同步与互斥——理发师问题多线程优化实践(sleeping barber problem)
  • 快速上手github项目:用快马一键生成标准开源仓库原型
  • iWrite 作文禁止粘贴时强行粘贴的方法
  • 轻量级跨平台安卓应用安装工具:APK-Installer极简高效使用指南
  • PCIe 5.0事务层深度解析:First/Last DW Byte Enables规则与TLP Header优化实践
  • 径向基RBF神经网络的故障分类与故障诊断的Matlab程序代码
  • Git学习
  • 【Agent】大模型在线API接入基础入门
  • 想把UC3842电源从12V1A升级到12V6A?这份保姆级物料清单与改造要点请收好
  • 新手友好:零基础使用快马AI生成专利数据链接展示页
  • 告别窗口限制:WindowResizer让Windows桌面管理效率提升300%
  • Windows Subsystem for Android (WSA) 技术指南:从问题诊断到场景落地的完整实践路径
  • 亲测高效降AI工具:高AI率论文1小时达标指南
  • 数字记忆守护者:GetQzonehistory实现QQ空间数据本地备份全攻略
  • WPF调试神器:如何在GUI应用中优雅地输出Console日志(附完整代码)
  • 前端CSS预处理器:别再写那些重复的CSS代码了
  • Windows系统指针美化全攻略:基于开源方案的跨平台实现
  • 三分钟搞定openclaw环境:用快马AI一键生成全平台安装脚本原型
  • Tesseract OCR 终极指南:5分钟掌握开源文字识别神器
  • SEO 优化者如何提高网站的转化率
  • 手把手教你用Burp Suite搞定PortSwigger Labs的CSRF靶场(附12个Lab实战POC)
  • Comsol弱形式求解三维光子晶体能带:快速而精确的模拟方法探索光子晶体的局域化光学行为
  • Visual C++运行库一站式解决方案:从依赖问题到高效部署
  • Spring Cloud OpenFeign实战:如何优雅地调用微服务接口(附完整代码示例)
  • 【C++27协程调试终极指南】:20年专家亲授5大不可外泄的断点追踪黑科技
  • Android WorkManager避坑指南:这样用才能真省电,而不是更耗电
  • simulink和carsim联合仿真的mpc轨迹跟踪模型。