相信很多读者关于 == 和 equals 懂了又懵, 懵了又懂, 如此循环, 事实上可能是因为看到的博客文章之类的太多了, 长篇大论, 加上一段时间的洗礼之后就迷路了. 本篇文章再一次理清楚. 当然如果觉得本文太啰嗦的话, 当然我也考虑到了, 因为我也不喜欢长篇大论啰里啰嗦比比叨叨胡搅蛮缠的文章, 毕竟大家入门 java 的时候就知道个大概了, 因此记住一句话就好了: equals 本身和 == 没有区别, 对于基本数据都是比较值, 对于引用类型, 则比较的是所指向的对象的地址! 其他类在继承 Object 类之后对 equals 方法重写, 所以表现的是比较里面的内容! 具体比较的则要看自己是怎么重写的.
好了, 如果有兴趣的就看下文, 当然不感兴趣的大佬可以点个赞直接走了, 不用看了, 会了还看个 * 啊, 楼主你个憨憨(皮一下很开心)
1, 原生的 equals()方法本身与 "==" 没有任何区别!
从 java 语言本质上来讲,"==" 属于 JAVA 语言的运算符, 而 equals 则是根类 Object 的一个方法.
关于 Object 类的 equals()方法, 我们可以看看其源码
- /*
- * @param obj the reference object with which to compare.
- * @return {@code true} if this object is the same as the obj
- * argument; {@code false} otherwise.
- * @see #hashCode()
- * @see java.util.HashMap
- */
- public boolean equals(Object obj) {
- return (this == obj);
- }
是的, equals 底层其实就是 "==", 也就是说, 原生的 equals()方法本身与 "==" 没有任何区别! 唯一的区别则是基本类型没有继承 Object 类, 所以基本类型没有 equals()方法, 也就是说基本类型只能使用 "==" 判断值是否相等.
既然原生的 equals()方法本身与 "==" 没有任何区别, 那么我们对运算符 "==" 的使用有所了解即可!
运算符 "==" 其具体作用是用来比较值是否相等, 这里分两中情况:
1, 基本数据类型的变量, 则直接比较其存储的 "值" 是否相等;
2, 引用类型的变量, 则比较的是所指向的对象的地址是否相等;
到这里我们可以初步确认原生的 equals()方法本身与 "==" 没有任何区别! 作用正是如上.
2,equals()方法的重写
但是重点来了, 因为对于 equals()方法我一直在强调原生二字. 是的, 让很多初学者疑惑的点就在这里: equals()方法的重写!
在 JDK 中, 诸如 String,Date 等类对 equals 方法进行了重写, 以 String 为例, 这里感兴趣的读者可以一起看看 String 类中重写的 equals()方法, 当然跳过也问题不大
- /**
- * Compares this string to the specified object. The result is {@code
- * true} if and only if the argument is not {@code null} and is a {@code
- * String} object that represents the same sequence of characters as this
- * object.
- *
- * @param anObject
- * The object to compare this {@code String} against
- *
- * @return {@code true} if the given object represents a {@code String}
- * equivalent to this string, {@code false} otherwise
- *
- * @see #compareTo(String)
- * @see #equalsIgnoreCase(String)
- */
- 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;
- }
从源码中可以看出, 首先该方法判断比较的是所指向的对象地址是否相等, 如果相同直接返回 true, 如果不相同进行下一个 if 判断, 第二个 if 判断大致意思则是比较其存储的 "值" 是否相等, 也就是比较内容值! 相同就返回 true, 比如两个 new String 对象 "AAA" 和 "AAA", 这里虽然对象地址不相等, 但是内容相等, 所以同样返回 true.
这里我就想给各位出个典型的 String 例子了:
- public static void main(String[] args) {
- String a = "宜春";
- String b = new String("宜春");
- String c = b; // 注意这里是引用传递, 意思是 c 也指向 b 指向的内存地址
- System.out.println(a == b); //false
- System.out.println(a == c); //false
- System.out.println(b == c); //true
- System.out.println(a.equals(b)); //true
- System.out.println(a.equals(c)); //true
- System.out.println(b.equals(c)); //true
- }
[特别注意: String 类型属于引用类型]
解析:
(1)a == b? 意思是地址指向的是同一块地方吗? 很明显不一样.
(2)a == c? 意思是地址指向的是同一块地方吗? 很明显不一样.
(3)b == c? 意思是地址指向的是同一块地方吗? 很明显内容一样, 所以为 true.
(4)a.equals( b )? 意思是地址指向的内容一样嘛? 一样.
(4)a.equals( c )? 意思是地址指向的内容一样嘛? 一样.
(4)b.equals( c )? 意思是地址指向的内容一样嘛? 一样.
当然, 你可能还是有点疑惑, 那么结合下面这张图再理解上面的解析, 你可能就恍然大悟了
OK. 现在能理解嘛? 你你你...... 不用回答, 我知道你理解了 (理直气壮). 当然值得注意一点的是 String 中 intern() 方法, 先看一段程序:
- public static void main(String[] args) {
- String a = "宜春";
- String b = new String("宜春");
- b=b.intern();
- System.out.println(a == b); //true
- System.out.println(a.equals(b)); //true
- }
intern 方法的意思是检查字符串池里是否存在, 如果存在了那就直接返回为 true. 因此在这里首先 a 会在字符串池里面有一个, 然后 b.intern()一看池子里有了, 就不再新建 new 了, 直接把 b 指向它.
3, 为什么要重写 equals 方法?
不知道大家有没有想过这个问题. 当然答案也是很简单的, 因为程序员比较字符串一般比较其内容就好了, 比较内存地址是不是同一个对象就好像没啥意义了, 重写 equals 方法就很方便用来比较字符串的内容了.
其实除了诸如 String,Date 等类对 equals 方法进行重写, 我们在实际开发中, 我们也常常会根据自己的业务需求重写 equals 方法.
举个栗子:
我们的需求就是如果两个学生对象姓名, 身份证号, 性别相等, 我们认为两个学生对象相等, 不一定需要学生对象地址相同.
学生 A 的个人信息(姓名: 如花, 性别: 女, 身份证号: 123, 住址: 广州), 学生 A 对象地址为 0x11,
学生 B 的个人信息(姓名: 如花, 性别: 女, 身份证号: 123, 住址: 深圳), 学生 A 对象地址为 0x12,
这时候如果不重写 Object 的 equals 方法, 那么返回的一定是 false 不相等, 这个时候就需要我们根据自己的需求重写 equals()方法了. 具体 equals 方法的重写代码如下:
- // 重写 equals 方法
- @Override
- public boolean equals(Object obj) {
- if(!(obj instanceof Student)) {
- // instanceof 已经处理了 obj = null 的情况
- return false;
- }
- Student stuObj = (Student) obj;
- // 对象地址相等
- if (this == stuObj) {
- return true;
- }
- // 如果两个对象姓名, 身份证号码, 性别相等, 我们认为两个对象相等
- if (stuObj.name.equals(this.name) && stuObj.sex.equals(this.sex) && stuObj.IDnumber.equals(this.IDnumber)) {
- return true;
- } else {
- return false;
- }
- }
开发中这样设计, 才能符合我们的生活! 到这里我就不信了你还搞不定 == 和 equals!
可是一涉及重写 equals 方法的同时又衍生了下面一个问题.
4, 重写 equals 方法之后要不要重写 hashCode()方法?
当然这个问题要讨论, 又要长篇大论哔哔一大堆了, 有空写一篇这样的文章吧专门讨论讨论这个问题, 当然园子里的大佬们也写了一大堆! 可以自行去了解了解. 本篇文章简单聊聊, 点到即可.
首先 hashCode()方法是 Object 类的一个方法, 源码如下:
- /**
- * Returns a hash code value for the object. This method is
- * supported for the benefit of hash tables such as those provided by
- * {@link java.util.HashMap}.
- * <p>
- * The general contract of {@code hashCode} is:
- * <ul>
- * <li>Whenever it is invoked on the same object more than once during
- * an execution of a Java application, the {@code hashCode} method
- * must consistently return the same integer, provided no information
- * used in {@code equals} comparisons on the object is modified.
- * This integer need not remain consistent from one execution of an
- * application to another execution of the same application.
- * <li>If two objects are equal according to the {@code equals(Object)}
- * method, then calling the {@code hashCode} method on each of
- * the two objects must produce the same integer result.
- * <li>It is <em>not</em> required that if two objects are unequal
- * according to the {@link java.lang.Object#equals(java.lang.Object)}
- * method, then calling the {@code hashCode} method on each of the
- * two objects must produce distinct integer results. However, the
- * programmer should be aware that producing distinct integer results
- * for unequal objects may improve the performance of hash tables.
- * </ul>
- * <p>
- * As much as is reasonably practical, the hashCode method defined by
- * class {@code Object} does return distinct integers for distinct
- * objects. (This is typically implemented by converting the internal
- * address of the object into an integer, but this implementation
- * technique is not required by the
- * Java<font size="-2"><sup>TM</sup></font> programming language.)
- *
- * @return a hash code value for this object.
- * @see java.lang.Object#equals(java.lang.Object)
- * @see java.lang.System#identityHashCode
- */
- public native int hashCode();
可以看出 hashCode()方法返回的就是一个 int 数值, 从方法的名称上就可以看出, 其目的是生成一个 hash 码. hash 码的主要用途就是在对对象进行散列的时候作为 key 输入, 据此很容易推断出, 我们需要每个对象的 hash 码尽可能不同, 这样才能保证散列的存取性能.
事实上, Object 类提供的默认实现确实保证每个对象的 hash 码不同(在对象的内存地址基础上经过特定算法返回一个 hash 码).Java 采用了哈希表的原理. 哈希算法也称为散列算法, 是将数据依特定算法直接指定到一个地址上. 初学者可以这样理解, hashCode 方法实际上返回的就是对象存储的物理地址(实际上不是).
想要知道 hashCode 的作用, 必须要先知道 Java 中的集合.
Java 中的集合 List 的元素是有序的, 元素可以重复; Set 元素无序, 但元素不可重复. 这我们都清楚. 但是你有没有想过这样一个问题: 要想保证元素不重复, 可两个元素是否重复应该依据什么来判断呢?
每错这里就是用 Object.equals 方法了. 但是, 如果每增加一个元素就检查一次, 那么当元素很多时, 后添加到集合中的元素比较的次数就非常多了. 也就是说, 如果集合中现在已经有 1000 个元素, 那么第 1001 个元素加入集合时, 它就要调用 1000 次 equals 方法. 这显然会大大降低效率.
那怎么解决呢? 我们可以在 Java 集合框架中得到验证. 由于 HashSet 是基于 HashMap 来实现的, 所以这里只看 HashMap 的 put 方法即可. 源码如下:
- public V put(K key, V value) {
- if (table == EMPTY_TABLE) {
- inflateTable(threshold);
- }
- if (key == null)
- return putForNullKey(value);
- int hash = hash(key);
- // 这里通过哈希值定位到对象的大概存储位置
- int i = indexFor(hash, table.length);
- for (Entry<K,V> e = table[i]; e != null; e = e.next) {
- Object k;
- //if 语句中, 先比较 hashcode, 再调用 equals()比较
- // 由于 "&&" 具有短路的功能, 只要 hashcode 不同, 也无需再调用 equals 方法
- if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }
- modCount++;
- addEntry(hash, key, value, i);
- return null;
- }
正如源码中注释所述,"&&" 具有短路的功能, 只要 hashcode 不同, 也无需再调用 equals 方法. 是的, Java 采用了哈希表的原理. 哈希表具有优越的查询性能, 就像九九乘法表 2*3=6 你能很轻易知道, 但是哈希表难免还会出现哈希冲突, 只是概率极低.
如此设计, 这样一来, 当集合要添加新的元素时, 先调用这个元素的 hashCode 方法, 就一下子能定位到它应该放置的物理位置上.
如果这个位置上没有元素, 它就可以直接存储在这个位置上, 不用再进行任何比较了; 如果这个位置上已经有元素了, 就调用它的 equals 方法与新元素进行比较, 相同的话就不存, 不相同就散列其它的地址. 所以这里存在一个冲突解决的问题. 这样一来实际调用 equals 方法的次数就大大降低了, 几乎只需要一两次.
因此并不是重写了 equals 方法就一定要重写 hashCode 方法, 只有用到 HashMap,HashSet 等 Java 集合的时候重写了 equals 方法就一定要重写 hashCode 方法. 用不到哈希表仅仅重写 equals()方法也 OK 的.
Java 官方建议 重写 equals()就一定要重写 hashCode()方法. 毕竟实际开发场景中常常用到 Java 集合
5,eqauls 方法和 hashCode 方法关系
Java 对于 eqauls 方法和 hashCode 方法是这样规定的:
1, 如果两个对象 equals 为 true , 他们的 hashcode 一定相等.
2, 如果两个对象 equals 为 false, 他们的 hashcode 有可能相等.
3, 如果两个对象 hashcode 相等, equals 不一定为 true.
4, 如果两个对象 hashcode 不相等, equals 一定为 false.
最后, 若有不足或者不正之处, 欢迎指正批评, 感激不尽!
欢迎各位关注我的公众号, 里面有一些 java 学习资料和一大波 java 电子书籍, 比如说周志明老师的深入 java 虚拟机, java 编程思想, 核心技术卷, 大话设计模式, java 并发编程实战..... 都是 java 的圣经, 不说了快上 Tomcat 车, 咋们走! 最主要的是一起探讨技术, 向往技术, 追求技术, 说好了来了就是盆友喔...
来源: https://www.cnblogs.com/yichunguo/p/12383853.html