HashMap的前置知识 - hashCode以及equals的前世今生
以下内容基于 JDK1.8
一、Object 类(package java.lang)
hashCode 方法
hashCode 方法是在 Object 类就有的方法。
public native int hashCode();
不过这个方法是 native 方法,即本地方法,底层调用时,调用的是非java语言的代码。
该方法根据对象的物理存储地址(internal address)生成一个整数。
equals 方法
equals 方法也是 Object 类中就有的方法,源代码如下:
public boolean equals(Object obj) {
return (this == obj);
}
即简单的比较两者的引用是否相同。
== 和 equals 方法的区别
- 对于 ==,如果作用于基本数据类型的变量,则直接比较值是否相等;
如果作用于引用类型的变量,则比较的是两者的引用。 - 对于 equals 方法,如果没有对 equals 方法进行重写,则比较的是对象的引用;
如果重写了 equals 方法,则执行的是自定义的 equals 方法。例如 String 等类对 equals 方法进行了重写,比较的是对象的内容。
什么时候需要重写 equals 方法和 hashCode 方法
由于在 Java 中所有的类都继承自 Object 类,所以所有的类都包含这两个方法,包括我们自定义的类。
当我们设计一个类时,如果我们需要判断该类的两个实例对象是否相等,就需要重写 equals 方法。重写的 equals 方法需要满足如下五个规则:
- 自反性:对任意引用值 x,x.equals(x) 的返回值一定为true。
- 对称性:对于任何引用值 x , y,当且仅当 y.equals(x) 返回值为 true 时,x.equals(y) 的返回值也为true。
- 传递性:如果 x.equals(y) = true,y.equals(z) = true,则 x.equals(z) = true。
- 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变。
- 非空性:任何非空的引用值 x,x.equals(null) 的返回值一定为 false。
如果这个类的对象会被存储在 HashMap 等数据结构中,那么就必须要重写 hashCode 方法和 equals 方法。
重写后的两个方法必须满足如下关系:
- 如果 equals 方法判断该类的两个实例对象相等,则两对象的 hashCode 方法的返回值也必须相等。
- 如果 equals 方法判断该类的两个实例对象不相等,则两对象的 hashCode 方法的返回值也尽量不等。
toString 方法(和主题不相关(^&^))
源代码如下:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
返回字符串,字符串内容为:类名+@+16进制的hashCode值
二、一些常见数据类型重写的 hashCode 方法以及 equals 方法
Integer 类
重写的 hashCode 方法
源代码如下:
@Override
public int hashCode() {
return Integer.hashCode(value);
}
public static int hashCode(int value) {
return value;
}
由此可见,Integer类的实例,调用其 hashCode 方法的返回值就是实例对象里所包含的整数值。
重写的 equals 方法
源代码如下:
private final int value;
public Integer(int value) {
this.value = value;
}
public int intValue() {
return value;
}
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
从源代码中不难看出,Integer 中的 equals 方法首先判断被比较对象 obj 是否为 Integer 或其子类的实例,如果是则直接比较对象中所包含的整数值是否相等,是则返回 true,否则返回 false。
Double 类
重写的 hashCode 方法
源代码如下:
@Override
public int hashCode() {
return Double.hashCode(value);
}
public static int hashCode(double value) {
long bits = doubleToLongBits(value);
return (int)(bits ^ (bits >>> 32));
}
/**
* Returns a representation of the specified floating-point value
* according to the IEEE 754 floating-point "double
* format" bit layout.
*/
public static long doubleToLongBits(double value) {
long result = doubleToRawLongBits(value);
// Check for NaN based on values of bit fields, maximum
// exponent and nonzero significand.
if ( ((result & DoubleConsts.EXP_BIT_MASK) ==
DoubleConsts.EXP_BIT_MASK) &&
(result & DoubleConsts.SIGNIF_BIT_MASK) != 0L)
result = 0x7ff8000000000000L;
return result;
}
稍微有点复杂,懂不懂无所谓,大致意思理解了就行,问题不大,看一下就可以了,感兴趣的可以自己去查阅相关资料。
重写的 equals 方法
源代码如下:
public boolean equals(Object obj) {
return (obj instanceof Double)
&& (doubleToLongBits(((Double)obj).value) ==
doubleToLongBits(value));
}
和 Integer 类中的 equals 方法类似,也是先判断被比较对象 obj 是否为 Double 或其子类的实例,如果是再进行值的比较。
这里给大家提个醒,示例代码如下:
Double a = 0d;
a.equals(0); //返回的是false
a == 0; //返回的是true
String 类
重写的 hashCode 方法
源代码如下:
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/**
* Returns a hash code for this string. The hash code for a
*String object is computed as
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
*/
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;
}
对于这段源代码,我们可以看到,String 底层是通过字符数组来操作管理字符串。注意,这个字符数组 value 是被声明为 final 的,表明 String 的设计者想让这个字符数组一旦被赋值就不再改变(由于 value 是引用类型,虽然它的值不能再改变,但是它所指向的地址里的内容可以改变,大家有兴趣可以去搜一下 final 关键字)。
而实际上也确实如此,其实从这段代码也可以看出设计者的这个意图,因为字符串的哈希码只在第一次调用 hashCode 方法时计算了一次,后面再调用 hashCode 方法都是直接返回 hash 的值。
有些童鞋可能会说源代码注释里明明说 hash 默认为0,但是也没见到哪里对它初始化为 0 呀(这是 Java 的一个小知识点,知道的可以跳过这段)。这是因为 int 型变量作局部变量时必须初始化,否则会编译报错,这是语法规定。但是作为类的成员变量时是默认初始化为 0 的。
从这段代码可以看到 String 类型哈希码的计算方式为 s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1](n 为字符串长度),源代码里使用了一点小技巧,绕了一个小弯弯。
重写的 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;
}
从源代码中可以看到,String 的 equals 方法首先比较两者的引用是否相等,不等的话判断被比较对象是否为 String及其子类的实例,如果是再优先判断两字符串长度是否相等,如果还为真,最后进行逐字符的比较。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 绘制分组散点图(克里夫兰点图)
- ggplot2绘制玫瑰图
- 绘制极坐标系条形图
- 四步重新认识冗余机器人的控制器设计
- 人脸识别接入常见问题汇总
- TKE上关于postStart 和preStop使用
- 文字识别接入常见问题
- 从 1 到 0 构建博客项目(2) -- 操作系统篇(2)--定制Centos
- 使用Angular依赖注入自定义SAP Spartacus的ProductAdapter
- 获取SAP Spartacus当前显示产品json数据的又一办法
- SAP Spartacus注入自定义的CurrentProductService
- Redis系列(十三)应用之分布式锁
- Oracle数据库 sql条件查询语句与练习
- (六)Hive优化
- mapreduce -- wordcount执行流程