对Java中HashCode方法的深入思考

前言最近在学习 Go 语言,Go 语言中有指针对象,一个指针变量指向了一个值的内存地址 。学习过 C 语言的猿友应该都知道指针的概念 。Go 语言语法与 C 相近,可以说是类 C 的编程语言,所以 Go 语言中有指针也是很正常的 。我们可以通过将取地址符&放在一个变量前使用就会得到相应变量的内存地址 。
package mainimport "fmt"func main() { var a int= 20 /* 声明实际变量 */ var ip *int /* 声明指针变量 */ ip = &a /* 指针变量的存储地址 */ fmt.Printf("a 变量的地址是: %xn", &a ) /* 指针变量的存储地址 */ fmt.Printf("ip 变量储存的指针地址: %xn", ip ) /* 使用指针访问值 */ fmt.Printf("*ip 变量的值: %dn", *ip )}复制代码因为本人主要开发语言是 JAVA,所以我就联想到 Java 中没有指针,那么 Java 中如何获取变量的内存地址呢?
如果能获取变量的内存地址那么就可以清晰的知道两个对象是否是同一个对象,如果两个对象的内存地址相等那么无疑是同一个对象反之则是不同的对象 。
很多人说对象的 HashCode 方法返回的就是对象的内存地址,包括我在《Java核心编程·卷I》的第5章内容中也发现说是 HashCode 其值就是对象的内存地址 。

对Java中HashCode方法的深入思考

文章插图
 
**但是 HashCode 方法真的是内存地址吗?**回答这个问题前我们先回顾下一些基础知识 。
==和equals在 Java 中比较两个对象是否相等主要是通过 ==号,比较的是他们在内存中的存放地址 。Object 类是 Java 中的超类,是所有类默认继承的,如果一个类没有重写 Object 的 equals方法,那么通过equals方法也可以判断两个对象是否相同,因为它内部就是通过==来实现的 。
//Indicates whether some other object is "equal to" this one.public boolean equals(Object obj) { return (this == obj);}复制代码
**Tips:**这里额外解释个疑惑
我们学习 Java 的时候知道,Java 的继承是单继承,如果所有的类都继承了 Object 类,那么为何创建一个类的时候还可以extend其他的类?
这里涉及到直接继承和间接继承的问题,当创建的类没有通过关键字 extend 显示继承指定的类时,类默认的直接继承了Object,A --> Object 。当创建的类通过关键字 extend 显示继承指定的类时,则它间接的继承了Object类,A --> B --> Object 。
这里的相同,是说比较的两个对象是否是同一个对象,即在内存中的地址是否相等 。而我们有时候需要比较两个对象的内容是否相同,即类具有自己特有的“逻辑相等”概念,而不是想了解它们是否指向同一个对象 。
例如比较如下两个字符串是否相同String a = "Hello" 和 String b = new String("Hello"),这里的相同有两种情形,是要比较 a 和 b 是否是同一个对象(内存地址是否相同),还是比较它们的内容是否相等?这个具体需要怎么区分呢?
如果使用 == 那么就是比较它们在内存中是否是同一个对象,但是 String 对象的默认父类也是 Object,所以默认的equals方法比较的也是内存地址,所以我们要重写 equals方法,正如 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;}复制代码这样当我们 a == b时是判断 a 和 b 是否是同一个对象,a.equals(b)则是比较 a 和 b 的内容是否相同,这应该很好理解 。
JDK 中不止 String 类重写了equals 方法,还有数据类型 Integer,Long,Double,Float等基本也都重写了 equals 方法 。所以我们在代码中用 Long 或者 Integer 做业务参数的时候,如果要比较它们是否相等,记得需要使用 equals 方法,而不要使用 == 。
因为使用 ==号会有意想不到的坑出现,像这种数据类型很多都会在内部封装一个常量池,例如 IntegerCache,LongCache 等等 。当数据值在某个范围内时会直接从常量池中获取而不会去新建对象 。
如果要使用==,可以将这些数据包装类型转换为基本类型之后,再通过==来比较,因为基本类型通过==比较的是数值,但是在转换的过程中需要注意 NPE(NullPointException)的发生 。
Object中的HashCodeequals 方法能比较两个对象的内容是否相等,因此可以用来查找某个对象是否在集合容器中,通常大致就是逐一去取集合中的每个对象元素与需要查询的对象进行equals比较,当发现某个元素与要查找的对象进行equals方法比较的结果相等时,则停止继续查找并返回肯定的信息,否则,返回否定的信息 。


推荐阅读