Java——对象比较
前言
本篇博客主要梳理一下Java中对象比较的需要注意的地方,将分为以下几个方面进行介绍:
- ==和equals()方法
- hashCode()方法和equals()方法
- Comparator接口和Comparable接口
==和equals()方法
在前面对String
介绍时,谈到过使用==
和equals()
去比较对象是否相等。
使用==
比较的是两个对象在内存中的地址是否一致,也就是比较两个对象是否为同一个对象。
使用equals()
方法可以依据对象的值来判定是否相等。
equals()方法是根类Object的默认方法,查看Object中equals()的默认实现:
public boolean equals(Object obj) {
return (this == obj);
}
可以看出没有重写过的equals()方法和==是一样的,都是比较两个对象引用指向的内存地址是否一样判断两个对象是否相等。
在介绍String时,我们发现并没有重写过equals()方法,但是可以使用equals()正确判断两个字符串对象是否相等。查看String源码可以发现是String本身重写了equals()方法。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
Java中很多类都自身重写了equals()方法,但是要使我们自定义的对象能正确比较,我们就需要重写equals()方法。
public class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override //此关键字可以帮助我们检查是否重写合乎要求
public boolean equals(Object obj) {
if (this == obj) //检测this与obj是否指向同一对象。这条语句是一个优化,避免直接去比较同一对象的各个域
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass()) // 比较this和obj是否属于同一个类 若是两个对象都不是同一个类的 则不相等
return false;
Student other = (Student) obj; //将obj转换成相应的Student类型
//对所有需要比较的域进行比较 基本类型使用== 对象域使用equal 数组类型的域,可以使用静态的Arrays.equals方法检测相应的数组元素是否相等
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
public static void main(String[] args) {
Student stu1 = new Student("sakura",20);
Student stu2 = new Student("sakura",20);
System.out.println(stu1.equals(stu2)); //output: true
}
}
以上重写的equals方法是考虑最为全面的,在重写时最好是照着这种格式来。若是使用eclipse,则有快捷键帮助我们自动生成此格式的equals方法。
hashCode()方法和equals()方法
可以从上图中看出,hashCode()
和equals()
是配套自动生成的,为什么要附加生成hashCode()呢。
hashCode()是根类Object中的默认方法,查看JDK:
hashCode()方法与equals()方法没有任何关系,hashCode()的存在是为了服务于建立在散列表基础上的类,如Java集合中的HashMap, HashSet等。hashCode()方法获取对象的哈希码(散列码)。哈希码是一个int型的整数,用于确定对象在哈希表(散列表)中的索引位置。
hashCode()方法会根据不同的对象生成不同的哈希值,默认情况下为了确保这个哈希值的唯一性,是通过将该对象的内部地址转换成一个整数来实现。
下面我们看一个例子:
public static void main(String[] args) {
Student stu1 = new Student("sakura",20);
Student stu2 = new Student("sakura",20);
HashSet<Student> stuSet = new HashSet<>();
stuSet.add(stu1);
stuSet.add(stu2);
System.out.println(stu1.equals(stu2));
System.out.println(stu1);
System.out.println(stu2);
System.out.println(stuSet);
}
/*
output:
true
prcatice.Student@7852e922
prcatice.Student@4e25154f
[prcatice.Student@7852e922, prcatice.Student@4e25154f]
*/
HashSet不会存储相同的对象。按理来说,stu1和stu2是相等的,不应该被重复放进stuSet里面。但是结果显示,出现了重复的对象。
但是stu1和stu2的hashCode()返回值不同,那么它们将会被存储在stuSet中的不同的位置。
对象存储在HashSet中时,先会根据对象的哈希值来查看是否哈希表中相应的索引位置是否有对象,若是没有则直接将对象插入;若是该位置有对象,则使用equals判断该位置上的对象与待插入的对象是否为相同对象,两个对象相等则不插入,不相等就将待插入对象挂在已存在对象的后面(就像链表一样挂载)。
总结来说就是:依据哈希值找位置,若是该位置没有对象则直接插入;若是有则比较,相等则不插入,不相等则悬挂在后面。
所以,要使stu1和stu2不能都被插入stuSet中,则要在Student中重写hashCode()方法。
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
在hashCode中为什么加入31这个奇素数来计算哈希值,总的目的是为了减少哈希冲突(在同一位置插入多个数)。详细理由可以参考此篇博文:为什么在定义hashcode时要使用31这个数呢?
然后我们在运行一次程序的输出如下:
/*
true
prcatice.Student@c9c6a694
prcatice.Student@c9c6a694
[prcatice.Student@c9c6a694]
*/
Comparator接口和Comparable接口
我们使用equals()方法可以实现比较我们自定义类的对象是否相等,但是却无法得到对象谁大谁小。Java中提供了两种方式来使得对象可以比较,实现Comparator
接口或者Comparable
接口。
Comparable接口
以able
结尾的接口都表示拥有某种能力。若是某个自定义类实现了comparable接口,则表示该类的实例化对象拥有可以比较的能力。
实现comparable接口需要覆盖其中的compareTo()方法(是一个泛型方法)。
int compareTo(T o)
返回负数:当前对象小于指定比较的对象;返回0,两个对象相等;返回正数,当前对象大于指定比较的对象。
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//重写comparaTo方法 以age作为标准比较大小
@Override
public int compareTo(Student o) {
return return (this.age<o.age ? -1 : (this.age == o.age ? 0 : 1));;//本类接收本类对象,对象可以直接访问属性(取消了封装的形式)
}
@Override
public String toString() {
return "name:" +name + " age:"+age;
}
public static void main(String[] args) {
Student stu1 = new Student("sakura",20);
Student stu2 = new Student("sakura",21);
Student stu3 = new Student("sakura",19);
//TreeSet会对插入的对象进行自动排序,所以要求知道对象之间的大小
TreeSet<Student> stuSet = new TreeSet<>();
stuSet.add(stu1);
stuSet.add(stu2);
stuSet.add(stu3);
//使用foreach(), lambda表达式输出stuSet中的值 forEach()方法从JDK1.8才开始有
stuSet.forEach(stu->System.out.println(stu));
}
}
/*
output:
name:sakura age:19
name:sakura age:20
name:sakura age:21
*/
实现了comparaTo()方法使用age为标准升序排序。也可以以name为标准排序,或者其他自定义的比较依据。
但是当Student已经实现了以age为依据从小到大排序后,我们又想以name为依据排序,在这个简单的程序中可以直接将return this.age-o.age
变为return this.name.compareTo(o.name)
(name为String对象),但是这样修改类结构会显得十分麻烦,万一在以后的程序中遇到的是别人封装好的类不能直接改类结构又该怎么办。
有没有其他方便的比较方法,实现对象的大小比较。
办法是有的,那就是实现Comparator接口。
Comparator接口
实现Comparator接口需要重写其中的compare()方法(一个泛型方法)。
int compare(T o1,T o2)
根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数,通常使用-1, 0, +1表示。
需要注意,Comparator接口中也有一个equals方法,但是这是判断该比较器与其他Comparator比较器是否相等。
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "name:"+name + " age:"+age;
}
public static void main(String[] args) {
Student stu1 = new Student("sakuraamy",20);
Student stu2 = new Student("sakurabob",21);
Student stu3 = new Student("sakura",19);
ArrayList<Student> stuList = new ArrayList<>();
stuList.add(stu1);
stuList.add(stu2);
stuList.add(stu3);
//没有必要去创建一个比较器类 采用内部类的方式实现Comparator接口
Collections.sort(stuList, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return (o1.age<o2.age ? -1 : (o1.age == o2.age ? 0 : 1));
//return o1.name.compareTo(o2.name);
}
});
//或者使用lambda表达式
//Collections.sort(stuList, (o1,o2)->o1.age-o2.age);
System.out.println(stuList);
}
}
/*
[name:sakura age:19, name:sakuraamy age:20, name:sakurabob age:21]
*/
由上可见,实现Comparator接口比较对象比实现Comparable接口简单和灵活。
使用这两个接口比较对象都需要注意几点:
- 对称性:若存在compare(x, y)>0 则 compare(y, x) <0,反之亦然
- 传递性:((compare(x, y)>0) && (compare(y, z)>0)) 可以推导出compare(x, z)>0
- 相等替代性:compare(x, y)0可以推导出compare(x, z)compare(y, z)
小结
简单总结一下本篇关于Java中对象比较的内容:要比较自定义类的对象是否相等需要重写equals()方法;
当对象要存储在建立在哈希表基础上的集合中时,还需要重写hashCode()方法用于判定对象在集合中的存储位置;
以某种依据比较对象的大小,可以实现Comparable接口或者Comparator接口,前者需要在类中实现表示该类拥有可以比较的能力,后者是在类外实现一个比较器,可以使用多种规则对对象进行比较,更灵活。
在Comparable中没有使用“简洁明了”的this.age-o.age作为返回值,是因为这是一个常见的编程错误,它至于在this.age和o.age都是无符号的int时才能正确工作。对于Java的有符号int,它就会出错。this.age是很大的正整数而o.age是很大的负整数,二者相减就会溢出从而产生负值,导致错误结果。
参考博文:https://juejin.im/entry/586c6a6061ff4b006407e2b9
Java——对象比较的更多相关文章
- Java对象序列化剖析
对象序列化的目的 1)希望将Java对象持久化在文件中 2)将Java对象用于网络传输 实现方式 如果希望一个类的对象可以被序列化/反序列化,那该类必须实现java.io.Serializable接口 ...
- 通过JAXB完成Java对象与XML之间的转换
Java对象转换XML的过程叫marshal. XML转换到Java对象的过程叫unmarshal. 一.Java对象转化为XML 这里省略getter和setter方法 通过标注@XMLRootEl ...
- json相关类库,java对象与json相互转换
有效选择七个关于Java的JSON开源类库 转自:http://www.open-open.com/lib/view/open1397870197828.html 翻译: (英语原文:http://w ...
- Hibernate 系列 07 - Hibernate中Java对象的三种状态
引导目录: Hibernate 系列教程 目录 1. Java对象的三种状态 当应用通过调用Hibernate API与框架发生交互时,需要从持久化的角度关注应用对象的生命周期. 持久化声明周期是Hi ...
- 理解Java对象序列化
http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html 1. 什么是Java对象序列化 Java平台允许我们在内存中创 ...
- java对象与XML相互转化
起因 最近在公司做了一次webservice相关的任务,其中我最敢兴趣的就是webservice接受到XML对应的流以后是如何方便的转化成java对象,而java对象又是如何生成对应的XML的. 目的 ...
- java 对象序列化与反序列化
Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为 ...
- Java对象大小计算
这篇说说如何计算Java对象大小的方法.之前在聊聊高并发(四)Java对象的表示模型和运行时内存表示 这篇中已经说了Java对象的内存表示模型是Oop-Klass模型. 普通对象的结构如下,按64位机 ...
- Java基础学习总结——Java对象的序列化和反序列化
一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存 ...
- java对象与json串互转
1:java对象与json串转换: java对象—json串: JSONObject JSONStr = JSONObject.fromObject(object); String str = JSO ...
随机推荐
- REdis之RDB配置问题
RDB配置:save 900 1save 300 10save 60 10000stop-writes-on-bgsave-error nordbcompression yesrdbchecksum ...
- struts2 升级至2.3.32时访问页面报错 File "/struts-tags" not found
Apache struts是美国阿帕奇(Apache)软件基金会负责维护的一个开源项目,是一套用于创建企业级Java Web 应用的开源MVC框架,主要提供两个版本框架产品: struts 1和str ...
- OC协议、代理的简单使用
在不同类之间传递数据,我所学到的有三种,1.代理,2.block,3.通知.在这里,我们先来讲一下代理的使用,后面我会继续讲到block和通知.代理通常和协议是一起使用的,协议通常写在代理类里面,被代 ...
- lambada
一.动态创建 ParameterExpression parameter = Expression.Parameter(typeof(User), "u"); //创建委托 Mem ...
- Javascript高级编程学习笔记(71)—— 模拟事件(1)DOM事件模拟
事件,指的是网页中某个特定的交互时刻 一般来说事件由浏览器厂商负责提供,一般由用户操作或者其它浏览器功能来触发 但是有一类特殊的事件,那就是由我们开发人员通过JS触发的事件 这些事件和浏览器创建的事件 ...
- [Swift-2019力扣杯春季初赛]3. 最小化舍入误差以满足目标
给定一系列价格 [p1,p2...,pn] 和一个目标 target,将每个价格 pi 舍入为 Roundi(pi) 以使得舍入数组 [Round1(p1),Round2(p2)...,Roundn( ...
- jupyter notebook的安装与基本操作
0.前言 最近正在重温Python基础知识,为了方便练习敲代码,于是选择安装jupyter notebook作为代码编辑器. Project Jupyter exists to develop ope ...
- extends的使用
继承extends的使用 继承(extends): 继承让我们可以更好的实现类的扩展. 继承的使用要点: 1.父类也称作超类.基类. ...
- Robot Framework - 建立本地测试环境
注意:本文内容是以“在Window7系统中安装本地RobotFrmamework自动化测试环境”为例. Robot Framework简介 HomePage:http://robotframework ...
- Jquery百宝箱
引入jquery <script src="https://blog-static.cnblogs.com/files/dongxiaodong/jquery-3.3.1.min.js ...