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

张开发
2026/5/4 22:38:48 15 分钟阅读
【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, 自定义类比较的是内存地址即判断两个引用是否指向堆内存中的同一个对象。inta10;intb10;System.out.println(ab);// true比较值Strings1newString(hello);Strings2newString(hello);System.out.println(s1s2);// false两个不同的对象内存地址不同Strings3world;Strings4world;System.out.println(s3s4);// true字符串常量池复用指向同一对象equals()equals()是Object类的一个方法。在Object类中它的默认实现就是使用即比较引用地址。// Object 类中的默认实现publicbooleanequals(Objectobj){return(thisobj);}因此如果在自定义类中不重写equals()方法那么obj1.equals(obj2)和obj1 obj2没有任何区别。真正的价值在于重写equals()。比较两个不同的对象时根据对象内部的属性值来判断它们是否“逻辑上相等”。这正是String类所做的。Strings1newString(hello);Strings2newString(hello);System.out.println(s1.equals(s2));// trueString重写了equals比较的是字符序列重写equals()时必须重写hashCode()先看一个错误的示例importjava.util.HashMap;classPerson{Stringname;intage;Person(Stringname,intage){this.namename;this.ageage;}// 只重写equals不重写hashCodepublicbooleanequals(Objectobj){if(thisobj)returntrue;if(objnull||getClass()!obj.getClass())returnfalse;Personperson(Person)obj;returnageperson.agename.equals(person.name);}}publicclassTest{publicstaticvoidmain(String[]args){HashMapPerson,StringmapnewHashMap();Personp1newPerson(Alice,20);Personp2newPerson(Alice,20);map.put(p1,Engineer);System.out.println(map.get(p2));// 输出 null 而不是 Engineer}}为什么会输出nullp1和p2根据equals判断是相等的。在HashMap中先通过hashCode()找到对象存储的“桶”位置。由于没有重写hashCode()p1和p2的哈希码大概率不同它们被放到了不同的桶里。用p2去查找时在p2的哈希码对应的桶里找不到p1因此返回null。约定与标准重写方式一致性如果两个对象通过equals()比较相等那么它们的hashCode()必须相等。非逆反如果两个对象的hashCode()相等它们的equals()不一定相等哈希冲突。标准重写模板以Person类为例使用Objects工具类importjava.util.Objects;classPerson{Stringname;intage;// ... 构造器、getter/setter ...Overridepublicbooleanequals(Objecto){if(thiso)returntrue;if(onull||getClass()!o.getClass())returnfalse;Personperson(Person)o;returnageperson.ageObjects.equals(name,person.name);}OverridepublicinthashCode(){returnObjects.hash(name,age);// 使用相同的关键字段计算哈希码}}提示现代IDE如IntelliJ IDEA、Eclipse都可以一键自动生成equals()和hashCode()非常方便推荐使用。顺序比较Comparable与Comparator除了判断相等经常需要对对象进行排序如Collections.sort(list)。这时就需要定义对象之间的自然顺序。内排序Comparable接口Comparable被称为内部比较器。一个类实现了Comparable接口就意味着它自身具备可比较的能力。publicinterfaceComparableT{publicintcompareTo(To);}返回负数当前对象 参数对象返回0当前对象 参数对象返回正数当前对象 参数对象示例让 Person 按年龄升序排序classPersonimplementsComparablePerson{Stringname;intage;// ... 构造器 ...OverridepublicintcompareTo(Persono){returnthis.age-o.age;// 按年龄升序// 注意避免整数溢出更稳妥写法return Integer.compare(this.age, o.age);}}// 使用ListPersonlistnewArrayList();Collections.sort(list);// 直接排序因为Person实现了Comparable优点一旦定义排序规则统一。缺点只能有一种排序规则且侵入到类内部。外排序Comparator接口Comparator被称为外部比较器。它不需要修改原始类的代码可以灵活地定义多种比较策略。publicinterfaceComparatorT{intcompare(To1,To2);}示例多种排序方式// 1. 按姓名升序ComparatorPersonnameComparatornewComparatorPerson(){Overridepublicintcompare(Personp1,Personp2){returnp1.name.compareTo(p2.name);}};// 2. 按年龄降序Lambda表达式更简洁ComparatorPersonageDescComparator(p1,p2)-p2.age-p1.age;// 3. 链式调用先按姓名再按年龄ComparatorPersoncomplexComparatorComparator.comparing(Person::getName).thenComparing(Person::getAge);// 使用Collections.sort(list,nameComparator);// 或 list.sort(ageDescComparator);优点灵活、解耦、支持链式调用和组合。缺点代码相对多一点但Lambda大大简化了。总结 ⭐特性equals()ComparableComparator类型运算符Object的方法接口java.lang接口java.util用途比较基本类型值或引用地址比较对象逻辑相等性定义对象的自然顺序定义自定义顺序需要重写否是对于自定义类是是定义匿名/独立实现侵入性无无有修改原类无常用场景基本类型比较、判断是否同一对象集合中查找、去重配合hashCodeCollections.sort(list)Collections.sort(list, comp)、多字段排序面试笔试题精选包装类 、 和equals()Integera127;Integerb127;System.out.println(ab);System.out.println(a.equals(b));Integerc128;Integerd128;System.out.println(cd);System.out.println(c.equals(d));A. true, true, false, trueB. true, true, true, trueC. true, false, false, trueD. false, true, false, true答案A解析valueOf() 源码分析部分hashCode()以下关于 hashCode() 的说法正确的是A. 两个对象相等hashCode 必须相等B. 两个对象 hashCode 相等则对象一定相等C. 重写 equals 时可以不重写 hashCodeD. 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.namename;this.ageage;}// 请补全 equals 和 hashCode 方法// 要求name 和 age 都相等时视为同一个对象// 你的代码写在这里 ↓// 你的代码写在这里 ↑}publicclassTest{publicstaticvoidmain(String[]args){HashSetPersonsetnewHashSet();set.add(newPerson(张三,20));set.add(newPerson(张三,20));set.add(newPerson(李四,22));System.out.println(set.size());// 期望输出 2}}参考答案Overridepublicbooleanequals(Objecto){if(thiso)returntrue;if(onull||getClass()!o.getClass())returnfalse;Personperson(Person)o;returnageperson.ageObjects.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想对集合按不同规则排序 → 传入ComparatorComparableh和Comparator 代码输出问题importjava.util.*;classStudentimplementsComparableStudent{Stringname;intscore;Student(Stringname,intscore){this.namename;this.scorescore;}OverridepublicintcompareTo(Studento){returnthis.score-o.score;}OverridepublicStringtoString(){returnname:score;}}publicclassMain{publicstaticvoidmain(String[]args){ListStudentlistnewArrayList();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 省略}// 请实现一个 ComparatorComparatorOrderorderComparator?参考答案// 先定义状态优先级映射MapString,IntegerstatusPriorityMap.of(待支付,1,已支付,2,已发货,3,已完成,4);ComparatorOrderorderComparatorComparator.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.xx;this.yy;}Overridepublicbooleanequals(Objectobj){Pointother(Point)obj;returnthis.xother.xthis.yother.y;}}答案及解析问题缺少 null 判断如果传入 null强制类型转换会抛出NullPointerException缺少类型判断如果传入的不是 Point 类型强制转换会抛出ClassCastException缺少hashCode()重写违反约定放入 HashSet/HashMap 会有问题缺少this obj优化缺少身份比较的性能优化修正后Overridepublicbooleanequals(Objectobj){if(thisobj)returntrue;if(objnull||getClass()!obj.getClass())returnfalse;Pointpoint(Point)obj;returnxpoint.xypoint.y;}OverridepublicinthashCode(){returnObjects.hash(x,y);}补充字符串的比较

更多文章