以下内容基于 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及其子类的实例,如果是再优先判断两字符串长度是否相等,如果还为真,最后进行逐字符的比较。
网友评论