网站首页 > java教程 正文
深入学习equals源码
最近在面试许多工作3-5年应聘者的时候,发现了许多人都没有阅读过String源码,尤其是equals和==的区别的问题以及停留在表层听说层次,没有深入理解。
首先,我们看一下Java的最顶级的基类Object的源码。该源码中包含了12个方法,那我们在开发的过程中常用的方法有5个,线程类中常用的有5个。剩下的finalize已经在jdk9中被标记deprecated,registerNatives是加载本地方法用的,用C语言开发的,咱们也不常用。
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);
}
上面简单的三行代码就是Object类中equals的源码。主要的比较是两个对象的地址。可以看到,方法体中equals比较其实也是“==”实现的。因此,下次在面试的过程中,你遇见问equals与“==”的区别的时候,你先给出结论。==是比较两个对象地址的,没有重写equals方法的实体类使用equals也是比较地址的。
那么我们经常用String中equals为什么是比较值的呢?接下来我们看一下String类中equals的源码
String中equals方法源码分析
/**
*@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) {
//可以看到,String中重新的equals方法在第一步就进行了地址比较,
//如果两个字符串的地址相等,那么他们的值一定相等,就不用去进行下面的值比较了
if (this == anObject) {
return true;
}
//这个if表示,如果equals的方法体中不是一个String类型,那么也就直接返回为false
//比如“蜜蜂攻城狮
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;
}
在我们实际编写代码的时候,特别是一些需要重写equals方法的实体类的时候,那么我们需要进行equals的方法重写,重写equals方法需要遵守如下约定。翻译如下
(1)自反性:x.equals(x)必须返回true。
(2)对称性:x.equals(y)与y.equals(x)的返回值必须相等。
(3)传递性:x.equals(y)为true,y.equals(z)也为true,那么x.equals(z)必须为true。
(4)一致性:如果对象x和y在equals()中使用的信息都没有改变,那么x.equals(y)值始终不变。
(5)非null:x不是null,y为null,则x.equals(y)必须为false。
String类中hashCode方法源码
这就涉及到我们上述在Obejct中看到的另一个方法。细心的朋友可能发现了,hashCode方法的修饰符是native。在Object类中有7个方法都是native修饰的。而在String类中hashCode源码如下。可以看到。该hashCode方法返回的是一个整形。它主要计算的是一个字符串的hash值然后将其缓存从而提高其性能,而计算的算法在注释中有提。那就是s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],算法中31是因为JVM对其进行了优化处理,即:31 * i == (i << 5) - i
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
重写equals必须重写hashCode的原因
重写hashCode的原则如下,也是我们必须遵守的,就等同于重写equals方法一样也需要遵守原则
(1)如果重写了equals()方法,检查条件“两个对象使用equals()方法判断为相等,则hashCode()方法也应该相等”是否成立,如果不成立,则重写hashCode ()方法。
(2)hashCode()方法不能太过简单,否则哈希冲突过多。
(3)hashCode()方法不能太过复杂,否则计算复杂度过高,影响性能。
其实,简单的来说。在我们重写hashCode方法的时候,我们一般会根据自身的业务来进行hashCode算法编写。正如String方法中的hashCode计算算法一样。我们根据业务涉及出自己所需要的算法。
那为什么我们重写了equals方法就需要重写hashCode方法呢?
其实有些人说的,重写了equals方法就一定要重新hashCode。这句话其实是错的。
因为hashCode和equals没有必然的区别。因为如果你不需要使用你定义的对象进行散列存储,比如使用hashMap,hashSet等集合,你不重写也没关系。因为hashCode本身是用来根据算法计算对象的散列值,然后根据这个值来决定存放位置。
举个简单的例子,如果使用HashMap,那么要保证key唯一,也就是要让其不重复,在java中比较是否相等用equals吧,所以使用equals去进行挨个比较,如果容器中已经存放了多个Key,是不是就需要比较很多次呢?如果有了hashCode值,是不是就简单多了。其实可以看一下hashmap的关于key比较的源码,就一清二楚了。在HashMap源码的625行putVal方法中可以看到634行及以后的内容,hashMap在put值的时候,其实首先用==去比较的是key的hash值。然后再用equals方法。
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
最后,也是最重要的一点,如果你重写了equals方法,在set,或hashmap容器中使用的时候,两个相等的对象有可能有不同的hashCode,这样就会导致容器中存放两个相同的对象,导致调用容器的方法出现逻辑错误。导致你的equals方法写了也白写,没有起到根本的作用。
又因为容器在我们日常开发中经常用到,因此才有了重写equals建议重写hashCode。也只是建议!
猜你喜欢
- 2024-12-30 推荐 33 个 IDEA 最牛配置,写代码太爽了
- 2024-12-30 java-cms源码 java源码之家
- 2024-12-30 “别把好好的Rust,写得跟Java一样”
- 2024-12-30 SpringBoot、MyBatis、Vue搭建一个Java企业应用开源框架源码分享
- 2024-12-30 Java并发实现原理—JDK源码剖析Atomic类:Striped64与LongAdder
- 2024-12-30 Java线程池源码深度解析 java中线程池原理
- 2024-12-30 这才是JAVA最好的低代码生成方案 java低代码开发平台
- 2024-12-30 java不练等于白学!(附源码) java学不懂
- 2024-12-30 小白7天掌握Shell编程:脚本的创建和执行
- 2024-12-30 分享一套SpringBoot开发博客系统源码,包含完整开发文档和视频
你 发表评论:
欢迎- 04-24Java Collections 工具类集合框架中常用算法解析
- 04-24桶排序的简单理解
- 04-24Java集合框架底层实现原理大揭秘
- 04-24Java 集合框架全面解析:选对数据结构,提升开发效率
- 04-24c#集合排序
- 04-24Java面试中常被问到的集合类深度解读
- 04-24VBA技术资料MF278:对集合进行排序
- 04-24Spring 最常用的 7 大类注解,史上最强整理
- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)