株洲品牌网站建设,网站备案程序,做网站需要什么配置的笔记本,试用网站 建站文章目录 万类之祖#xff1a;Object 类hashCode() 与 equals()hashCode() 方法equals() 方法 vs. equals()String 的 equals() 为什么要重写 hashCode 和 equals 方法重写#xff08;覆盖#xff09;前 hashCode() 和 equals() 的作用什么情况下需要重写#xff08;覆盖Object 类hashCode() 与 equals()hashCode() 方法equals() 方法 vs. equals()String 的 equals() 为什么要重写 hashCode 和 equals 方法重写覆盖前 hashCode() 和 equals() 的作用什么情况下需要重写覆盖 hashCode() 和 equals() 为什么重写equals() 方法一定要重写 hashCode() 方法 自动覆盖 equals() 与 hashCode() toString() 方法自动覆盖 toString() Objects 类 万类之祖Object 类
所有的类除了 Object 类本身都间接或者直接地继承自 Object 类。
Object 类只有成员方法没有成员变量。
在 IDEA 中菜单栏 -- Navigate -- Type Hierarchy 或直接 ctrl H
可以看到当前类的类继承关系。
可以看到无论是哪条类继承链最上方总会是 Object 类 java.lang.Object。
这也是为什么在 IDEA 中写代码时经常在输入点操作符.后会弹出一大堆的不是我们自己编写的方法提示/推荐让我们选择因为这些弹出来推荐使用的方法都是 Object 类中定义的而我们自己编写的类所创建的对象自然会继承这些方法。 我们虽然没有显式在自定义类中 extends Object但 java 会给我们自动补上 extends Object。当然你自定义一个类非自己写上比如说 public class TestUse extends Object 也没有问题测试过并不会报错。但 IDEA 会提示你可以删除冗余的 extends Object。 package org.test.extendstest.statictest;public class TestUse {public static void main(String[] args) {Object obj new Object();System.out.println(Object 类的实例信息obj); // Object 类的实例信息java.lang.Object404b9385A a new A();System.out.println(打印 A 类的实例信息); // 打印 A 类的实例信息printObj(a);}public static void printObj(Object obj){System.out.println(obj);// org.test.extendstest.statictest.A682a0b20System.out.println(obj.toString()); // org.test.extendstest.statictest.A682a0b20System.out.println(obj.getClass());// class org.test.extendstest.statictest.ASystem.out.println(obj.hashCode());// 1747585824// 1747585824 转换成 16 进制就是 682a0b20}
}代码详细解析如下
package org.test.extendstest.statictest;public class TestUse {public static void main(String[] args) {Object obj new Object();System.out.println(Object 类的实例信息obj);// 当然null 直接用 System.out.println(null) 是可以正常输出 “null” 的。
// printObj((Object)null); // 如果不注释会报错因为 null 无法调用 .toString、.getClass、.hashCode 函数
// printObj(null); // 如果不注释会报错因为 null 无法调用 .toString、.getClass、.hashCode 函数A a new A();System.out.println(打印 A 类的实例信息);printObj(a);}public static void printObj(Object obj){System.out.println(obj);// 其实在 IDEA 中 ctrl 点击 println 进去看代码会发现经过相关调用跳转最终调用的函数是/*public static String valueOf(Object obj) {return (obj null) ? null : obj.toString();}*/// 也就是会判断是不是 null 是 null 就输出 “null”不是的话就调用 .toString()所以输出和下方的 .toString() 一样。// 看看实现System.out.println(obj.toString()); // 查看 .toString() 源码/*public String toString() {return getClass().getName() Integer.toHexString(hashCode());}*/// 可以看到输出的是一个拼接的字符串分别就有调用下方的 .getClass() 和 .hashCode()// 这里 .toHexString() 就是转 16 进制也就是把 hashCode() 返回的哈希值转成 16 进制// native 方法。// java 的类库里有许多 native 方法native 的意思是这个方法没有方法体它方法的代码实际上是用本地操作系统平台相关比如只能跑在 windows 或只能跑在 mac OS 上的代码实现的C 或 C。它存在一个映射机制根据方法名对应到本地的 c/c 写的和平台相关的方法。System.out.println(obj.getClass()); System.out.println(obj.hashCode());/*IntrinsicCandidatepublic final native Class? getClass(); // java 源码中没有方法体方法体在计算机的某个 dll 中定义*//*IntrinsicCandidatepublic native int hashCode(); // java 源码中没有方法体方法体在计算机的某个 dll 中定义*/}
}// 输出结果
// Object 类的实例信息java.lang.Object404b9385
// 打印 A 类的实例信息
// org.test.extendstest.statictest.A682a0b20
// org.test.extendstest.statictest.A682a0b20
// class org.test.extendstest.statictest.A
// 1747585824// 1747585824 转换成 16 进制就是682a0b20
// 哈希码是一个标识相对比较唯一地去标识一个对象但不确保完全唯一因为存在哈希碰撞两个对象刚好同一个哈希值但只要是哈希值哈希码不同那对象就肯定是不同的hashCode() 与 equals()
参考
https://blog.csdn.net/qq_50838572/article/details/122877342 hashCode() 和 equals() 这两个基本上是初级 java 程序员面试必考的内容。 hashCode() 和 equals() 是最常覆盖的两个方法。覆盖原则是equals() 为 true hashCode 就应该相等。这是一种约定俗成的规范。
equals() 是 true 是 hashCode() 相等的充分不必要条件hashCode() 相等是 equals() 为 true 的必要不充分条件。 e q u a l s ( ) 是 t r u e ⇒ h a s h C o d e ( ) 相等 equals() 是 true\Rightarrow hashCode() 相等 equals()是true⇒hashCode()相等 h a s h C o d e ( ) 相等 ⇏ e q u a l s ( ) 是 t r u e hashCode() 相等 \nRightarrow equals() 是 true hashCode()相等⇏equals()是true hashCode() 方法
hashCode 可以翻译为 “哈希码”或者 “散列码”是一个表示对象的特征值的 int 整数。
当没有任何类去覆盖 hashCode() 可以简单地认为哈希码的值就是这个对象在内存中的地址Object 中 hashcode() 是根据对象的存储地址转换而形成的一个哈希值。
// Object 的 hashCode() 是 native 方法因此在 java 源码中是没有方法体的。它本质上是用 C 来实现的。
IntrinsicCandidate
public native int hashCode();equals() 方法
equals 方法应该用来判断两个对象从逻辑上是否相等。并不是判断这两个对象是不是同一个对象 这里之所以是“应该”是因为如果不覆盖的 equals() 那么就是继承 Object 类中的 equals() 方法但这个原始的 equals() 就真的仅仅是在判断两个对象是不是同一个对象。这样是没什么意义的所以要通过覆盖让 equals() 真正去判断两个对象在业务逻辑上是否相等。 vs. equals() 在引用数据类型当中进行的是地址的比较equals() 方法在 Object 类当中其底层也是用 比较地址但是不同的类可能会重写equals() 方法比如 String 类中的 equals() 方法就是先比较地址是否相同如果相同直接返回 true地址不同再比较值如果值相等那么同样返回 true。
// Object 类中的 equals()
public boolean equals(Object obj) {return (this obj);
}// String 类中覆盖的 equals()
public boolean equals(Object anObject) {if (this anObject) {return true;}return (anObject instanceof String aString) (!COMPACT_STRINGS || this.coder aString.coder) StringLatin1.equals(value, aString.value);
}String 的 equals()
因为在 java 中 String 类实在用得太多了对象创建也太多了所以 java 针对 String 添加了一些优化。
public class StringEqualsTest {public static void main(String[] args) {String s1 aaabbb;String s2 aaa bbb;System.out.println(用 判断结果(s1 s2)); // 用 判断结果trueSystem.out.println(用 equals 判断结果 s1.equals(s2)); // 用 equals 判断结果true}
}这个输出结果可能会让人惊讶。因为按道理 比较的是对象的实际地址也就是判断两个引用对象是否是同一个对象而字符串是不可变的拼接实际上是创建一个新的对象因此 s1 与 s2 本应该是两个不同的对象但输出却不是 false 而是 true。
这是因为 java 对 String 类对象做了特殊的优化 java 会有一个专门的地方用来放字符串如果创建的字符串不是特别长而且整个程序运行的时候字符串创建也没有太多时就把这些创建的字符串放到一个地方。当你创建一个新的字符串时Java 会先去那个地方去找看有没有一样的字符串如果有的话就直接返回这个字符串的引用而不重新创建一个字符串。 这是因为本来 String 类的对象就是不可变的你即便创建一个新的 String 对象和沿用之前创建的值一样的是没有任何区别的。所以 java 可以放心地做这种事情。 所以 s1 和 s2 实际上指向的就是同一个对象。 但 java 对 String 的优化也是有限制的如果太长了突破了这个限制比如创建的 s1 很长很长s2 也很长很长那么即便已经创建了 s1s2 的值和 s1 一样但仍旧会给 s2 重新创建一个新的对象。此时用 判断则 s1 与 s2 是不相同的。但用 equals() 判断因为值实际是一样的所以 s1 与 s2 是相等的。 Kevin对上面这个“限制”的说法Kevin 表示存疑。因为实际上试过很长很长的IDEA中String声明中赋值的常量字符串最多不超过65535字符否则报错常量字符串过长。所以就用了极限不报错的长度但仍然 的结果为 true。 再次尝试测试代码如下 import java.util.Scanner;public class StringEqualsTest {public static void main(String[] args) {String s1 aaabbb;String s2 aaa bbb;System.out.println(用 判断结果(s1 s2)); // trueSystem.out.println(用 equals 判断结果 s1.equals(s2)); // trueScanner scanner new Scanner(System.in);System.out.println(请输入 s3);String s3 scanner.nextLine(); // 输入 aaabbbSystem.out.println(请输入 s4);String s4 scanner.nextLine(); // 输入 aaabbbSystem.out.println(用 判断结果 (s3 s4)); // falseSystem.out.println(用 equals 判断结果 s3.equals(s4)); // trueSystem.out.println(s1 s3(s1s3)); // falseSystem.out.println(s1.equals(s3)s1.equals(s3)); // trueString s5 new String(aaabbb);String s6 new String(aaabbb);System.out.println(用 判断结果 (s5 s6)); // falseSystem.out.println(用 equals 判断结果 s5.equals(s6)); // trueSystem.out.println(用 判断结果 (s1 s5)); // falseSystem.out.println(用 equals 判断结果 s1.equals(s5)); // trueSystem.out.println(用 判断结果 (s3 s5)); // falseSystem.out.println(用 equals 判断结果 s3.equals(s5)); // trueStringBuilder s7 new StringBuilder(aaa);StringBuilder s8 new StringBuilder(aaa);s7.append(bbb);s8.append(bbb);System.out.println(s7s8); // falseSystem.out.println(s7.toString()s8.toString()); // falseSystem.out.println(s7.toString().equals(s8)); // falseSystem.out.println(s7.toString().equals(s8.toString())); // trueSystem.out.println(s7.toString()s1); // falseSystem.out.println(s7.toString().equals(s1)); // true}
}发现只要是通过 Scanner 输入的字符串、或者new String() 创建的字符串无论多短即便值相同也不会是同一个对象即 永远为 false。 Kevin所以一开始的 s1 与 s2 之所以相等和字符串长度应该没有关系当然这也的确有 java 对 String 类的优化在里面。但这里主要应该是因为String s1 aaabbb;中aaabbb是显式字符串常量而不被认为是对象对象应该放于堆中存放在常量池里。但 new String(aaabbb)存放在内存的堆中。而无论是 new String() 、new StringBuilder() 还是 Scanner的 nextLine() 实际上都创建了新的 String 对象只是对象中存放的值相等罢了所以 java 对 String 的优化仅仅是用在了 “显式字符串常量” 上而与字符串长短无关。
鉴于这种不统一的表现用 来判断两个引用指向的字符串是否相等是不靠谱的。
因此在实际判断两个 String 类对象是否相等应该使用 equals() 而非 。
为什么要重写 hashCode 和 equals 方法
重写覆盖前 hashCode() 和 equals() 的作用
重写前equals() 与 hashCode() 源代码如下由于 hashCode() 是 native method所以在 java 源代码中没有方法体
// Object 类中的 equals()效果是直接判断两个对象是否为同一个对象
public boolean equals(Object obj) {return (this obj);
}// Object 类中的 hashCode()效果是根据对象的存储地址转换为一个哈希值int 类型值
IntrinsicCandidate
public native int hashCode();什么情况下需要重写覆盖 hashCode() 和 equals()
可以看到Object 的 equals() 真的没什么意义仅仅是判断这个对象是不是它自己而业务中我们通常是需要对比的是两个不同的对象比较它们的业务逻辑意义上是否相同即它们包含的数据是否相同。如果相同则判断它们相同所以equals() 需要根据程序员自己的业务逻辑去覆盖仅仅沿用 Object 中定义的 equals() 意义不大。
同样Object 中的 hashCode() 是根据对象的实际地址映射出来的一个 int 整数如果用 hashCode() 来判断两个对象是否相同也没有太大意义。因为除去有可能不同对象发生哈希碰撞判断的仍旧仅仅是这个对象是否是它自身。
所以“什么情况下需要重写覆盖 hashCode() 和 equals() ”的答案是根据具体业务需求来重写这两个方法。
为什么重写equals() 方法一定要重写 hashCode() 方法
那么问题来了如果现在想比较两个不同对象中包含的数据是否一致如果一致就判断两个对象相同那么似乎只重写 equals() 就够了在 equals() 中把两个对象的所有成员变量或者业务逻辑中重视的那几个成员变量即可以不将所有成员变量一一对比而只对比你想对比的并依比较结果判断两个对象在业务逻辑上是否相等挨个比较一下不就可以了吗为什么还得把 hashCode() 重写呢
原因是这样的 很多时候我们的需求不是光判断这两个对象所包含的数据是否相同然后仅仅作出一个对象是否相同的判断。这个行为背后是有下一步的比如说在 java 的一个容器里容器中的对象是不允许重复的而是否重复就是由 hashCode 来判断。比如 HashSet 或者 HashMap 的 key是不允许重复的。现在的场景可能是我需要往一个这样的容器中放入对象如果对象是和容器中已经存在的对象相同我就让它覆盖如果不同那就正常放入这个新的对象。 比如说容器里的对象只有一个 age 属性现在存在 A 对象和 B 对象A.age 13; B.age 13那么这两个对象就应该判断为相同的通过重写 equals()但是如果不重写 hashCode() 那么这两个对象因为内存地址是不同的也就是说 hashCode() 的返回值是不同的就会被 java 认为是不同的两个对象可以同时放入到这个容器中而不会发生覆盖行为。 HashMap 是一个散列表它存储的内容是键值对key-value映射。该类实现了 Map 接口根据键的 HashCode 值存储数据具有很快的访问速度最多允许一条记录的键为 null不支持线程同步。 HashSet 可以认为 HashSet 是简化版的 HashMap但存储的内容不是键值对key-value映射可以认为 HashSet 仅仅实现了 key 的部分。该类实现了 Set 接口不允许出现重复元素不保证集合中元素的顺序允许包含值为 null 的元素但最多只能一个。
下面看一个只重写 equals() 而不重写 hashCode() 的例子
// 只重写 equals() 而不重写 hashCode()
public class Dog {private String name;public Dog(String name) {this.name name;}// 重写 equals()Overridepublic boolean equals(Object o) {if (this o) return true;if (!(o instanceof Dog)) return false;Dog dog (Dog) o;return name.equals(dog.name);}Overridepublic String toString() {return { Dog: name };}}// 调用类
import java.util.HashSet;public class TestUse {public static void main(String[] args) {Dog a new Dog(Tom);Dog b new Dog(Tom);Dog c new Dog(Jerry);System.out.println(a b); // falseSystem.out.println(a.equals(b)); // trueSystem.out.println(a c); // falseSystem.out.println(a.equals(c)); // falseHashSet hashset new HashSet();hashset.add(a);hashset.add(b);hashset.add(c);System.out.println(hashset); // [{ Dog: Jerry }, { Dog: Tom }, { Dog: Tom }]}
}
// 输出结果
// false
// true
// false
// false
// [{ Dog: Jerry }, { Dog: Tom }, { Dog: Tom }]// 这样就很别扭了a 和 b 应该是当作同一只狗的但方法 hashset 里却没有覆盖掉。下面再看一个同时重写了 equals() 与 hashCode() 的例子
import java.util.Objects;public class Dog {private String name;public Dog(String name) {this.name name;}// 重写 equals()Overridepublic boolean equals(Object o) {if (this o) return true;if (!(o instanceof Dog)) return false;Dog dog (Dog) o;return name.equals(dog.name);}// 重写 hashCode()// 现在一般都不自己重写 hashCode()都把它交给 IDEA 去自动生成因为 hashCode 已经有一套相对标准的生成流程了我们只需要让 IDEA 帮我们生成就好。Overridepublic int hashCode() {return Objects.hash(name); // 用一个静态方法 Objects.hash()来生成哈希码}Overridepublic String toString() {return { Dog: name };}
}// 调用类
import java.util.HashSet;public class TestUse {public static void main(String[] args) {Dog a new Dog(Tom);Dog b new Dog(Tom);Dog c new Dog(Jerry);System.out.println(a b); // falseSystem.out.println(a.equals(b)); // trueSystem.out.println(a c); // falseSystem.out.println(a.equals(c)); // falseHashSet hashset new HashSet();hashset.add(a);hashset.add(b);hashset.add(c);System.out.println(hashset); // [{ Dog: Tom }, { Dog: Jerry }]}
}
// 输出结果
// false
// true
// false
// false
// [{ Dog: Tom }, { Dog: Jerry }]// 这样一来就可以覆盖掉相同的对象了业务逻辑就变得合理了。自动覆盖 equals() 与 hashCode()
其实在 IDEA 中可以直接让 IDEA 自动生成覆盖的 equals() 与 hashCode() 源代码空白处鼠标右击 -- Generate...(Alt Insert) -- equals() and hashCode()。 里面的选项见到的都打勾就好了。
toString() 方法
toString() 就是把类里的信息通过生成 String描述出来。
自动覆盖 toString()
其实在 IDEA 中可以直接让 IDEA 自动生成覆盖的 toString() 源代码空白处鼠标右击 -- Generate...(Alt Insert) -- toString()。 选择要描述的属性可以选择其中几个或全选然后点击 OK。
// 自动生成的 toString() 类似下面格式
Override
public String toString() {
return Dog{
name name \ // 其实这里的 name指标黑色的成员变量其实就是调 toString() 方法得到一个 String然后拼接。
};
}// 如果这些属性本身是引用类型其实就是调用它们的 toString 方法返回一个 String然后再把这些 String 拼接。
// 或许在 java 程序中显式地看到 toString() 的调用并不多但实际上对 toString(默默地调用还是很多的。toString() 方法不仅仅是在调用的时候有用在 debug 时IDEA 其实也会调用对象的 toString() 方法帮我们获取到这个对象的状态信息并展示出来方便我们调试。如 debug 时显示在每行运行过的源代码右边的灰色的信息其实调用的就是 toString()底部的监控信息也是调用的 toString()当然下方的对象信息还可以进一步点击左方的尖括号展开看对象信息但没展开前实际上就是调用的 toString()和我们定义的 toString() 的格式一致帮助我们看到有数据的类的 实例/对象 里面是什么数据 public class Dog {private String name;public Dog(String name) {this.name name;}Overridepublic boolean equals(Object o) {if (this o) return true;if (!(o instanceof Dog)) return false;Dog dog (Dog) o;return name.equals(dog.name);}Overridepublic int hashCode() {return Objects.hash(name);}// 覆盖定义的 toString()Overridepublic String toString() {return Dog{ name name \ };}
}Objects 类
Objects 类与 Object 类是不同的要注意区别。查看 Objects 类的继承关系会发现 Objects 类也是继承自 Object 类的。
Object(java.lang)||--- Objects(java.util)使用 java.lang 包的类是无需导入的但使用 java.util 包里的类是需要 import 导入的。
再看看两个相似但不同的函数equals()
// Objects 类里的 equals()
public static boolean equals(Object a, Object b) { return (a b) || (a ! null a.equals(b));
}// Object 类里的 equals()
public boolean equals(Object obj) { return (this obj);
}
可以看到 Objects 类里的 equals() 是静态方法而 Object 类里的 equals() 是实例方法。
而且从程序逻辑来说Object 的 .equals() 是判断两个对象是不是同一个对象使用 而 Objects.equals() 则是会判断两个对象是不是同一个对象或非空的情况下调用 Object 的 .equals()。