本篇博客主要梳理一下Java中对象比较的须要注意的地方,将分为如下几个方面进行介绍:<br>html
在前面对String
介绍时,谈到过使用==
和equals()
去比较对象是否相等。<br> 使用==
比较的是两个对象在内存中的地址是否一致,也就是比较两个对象是否为同一个对象。<br>使用equals()
方法能够依据对象的值来断定是否相等。<br>编程
equals()方法是根类Object的默认方法,查看Object中equals()的默认实现:数组
public boolean equals(Object obj) { return (this == obj); }
能够看出<u>没有重写过的equals()方法和==是同样的,都是比较两个对象引用指向的内存地址是否同样判断两个对象是否相等</u>。<br> 在介绍String时,咱们发现并无重写过equals()方法,可是可使用equals()正确判断两个字符串对象是否相等。查看String源码能够发现是String自己重写了equals()方法。<br>eclipse
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()方法。<br>ide
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方法。<br>优化
<br>this
能够从上图中看出,hashCode()
和equals()
是配套自动生成的,为何要附加生成hashCode()呢。<br> hashCode()是根类Object中的默认方法,查看JDK:spa
<b>hashCode()方法与equals()方法没有任何关系</b>,hashCode()的存在是为了服务于创建在散列表基础上的类,如Java集合中的HashMap, HashSet等。hashCode()方法获取对象的哈希码(散列码)。<b>哈希码是一个int型的整数,用于肯定对象在哈希表(散列表)中的索引位置</b>。<br>.net
<u>hashCode()方法会根据不一样的对象生成不一样的哈希值,默认状况下为了确保这个哈希值的惟一性,是经过将该对象的内部地址转换成一个整数来实现。</u><br> 下面咱们看一个例子:3d
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里面。可是结果显示,出现了重复的对象。<br> 可是stu1和stu2的hashCode()返回值不一样,那么它们将会被存储在stuSet中的不一样的位置。<br> <i>对象存储在HashSet中时,先会根据对象的哈希值来查看是否哈希表中相应的索引位置是否有对象,如果没有则直接将对象插入;如果该位置有对象,则使用equals判断该位置上的对象与待插入的对象是否为相同对象,两个对象相等则不插入,不相等就将待插入对象挂在已存在对象的后面(就像链表同样挂载)。</i><br> 总结来讲就是:<b>依据哈希值找位置,如果该位置没有对象则直接插入;如果有则比较,相等则不插入,不相等则悬挂在后面。</b>
因此,要使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] */
<br>
咱们使用equals()方法能够实现比较咱们自定义类的对象是否相等,可是却没法获得对象谁大谁小。Java中提供了两种方式来使得对象能够比较,实现Comparator
接口或者Comparable
接口。
<font color="brown"><b>Comparable接口</b></font><br> 以able
结尾的接口都表示拥有某种能力。如果某个自定义类实现了comparable接口,则表示<b>该类的实例化对象拥有能够比较的能力</b>。 实现comparable接口须要覆盖其中的compareTo()方法(是一个泛型方法)。
int compareTo(T o)
<u>返回负数:当前对象小于指定比较的对象;返回0,两个对象相等;返回正数,当前对象大于指定比较的对象。</u><br>
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为标准排序,或者其余自定义的比较依据。<br> 可是当Student已经实现了以age为依据从小到大排序后,咱们又想以name为依据排序,在这个简单的程序中能够直接将return this.age-o.age
变为return this.name.compareTo(o.name)
(name为String对象),可是这样修改类结构会显得十分麻烦,万一在之后的程序中遇到的是别人封装好的类不能直接改类结构又该怎么办。<br> 有没有其余方便的比较方法,实现对象的大小比较。 办法是有的,那就是实现Comparator接口。
<font color="brown"><b>Comparator接口</b></font><br> 实现Comparator接口须要重写其中的compare()方法(一个泛型方法)。
int compare(T o1,T o2)
根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数,一般使用-1, 0, +1表示。<br>
须要注意,Comparator接口中也有一个equals方法,可是这是判断<b>该比较器与其余Comparator比较器是否相等。</b><br>
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接口简单和灵活。<br> 使用这两个接口比较对象都须要注意几点:
简单总结一下本篇关于Java中对象比较的内容:要比较自定义类的对象是否相等须要重写equals()方法;<br>当对象要存储在创建在哈希表基础上的集合中时,还须要重写hashCode()方法用于断定对象在集合中的存储位置;<br>以某种依据比较对象的大小,能够实现Comparable接口或者Comparator接口,前者须要在类中实现表示该类拥有能够比较的能力,后者是在类外实现一个比较器,可使用多种规则对对象进行比较,更灵活。<br>
在Comparable中没有使用“简洁明了”的this.age-o.age做为返回值,是由于这是一个常见的编程错误,它至于在this.age和o.age都是无符号的int时才能正确工做。对于Java的有符号int,它就会出错。this.age是很大的正整数而o.age是很大的负整数,两者相减就会溢出从而产生负值,致使错误结果。
参考博文:https://juejin.im/entry/586c6a6061ff4b006407e2b9
原文出处:https://www.cnblogs.com/myworld7/p/10050947.html