「JDK源码之Object类」
写在前面
本文依据 JDK8 源码进行编写
关于 native 修饰的方法不进行内部剖析
只对核心方法进行剖析
什么是 native 方法?官方给的说明是"A native method is a Java method whose implementation is provided by non-java code." 通俗的说,native 表示该方法的实现 java 本身并没有完成,而是有 c/c++来完成,放在.dll 动态库文件中。
Object 类结构

类构造器
Object 类没有显式声明构造器,因此 Object 类使用的是默认的无参构造器。
equals 方法
源码:
public boolean equals(Object obj) {
return (this == obj);
}从源码中可以得知,在 Object 类中,== 运算符和 equals 方法是等价的,都是比较两个对象的引用是否相等,从另一方面来讲,如果两个对象的引用相等,那么这两个对象一定是相等的。
对于我们自定义的一个对象,如果不重写 equals 方法,那么在比较对象的时候就是调用 Object 类的 equals 方法,也就是用 == 运算符比较两个对象。
思考:
equals 方法和 == 运算符的区别?我们应该如何重写 equals 方法?
一般从面试题中我们可以得知:== 运算符可以用来比较基本数据类型,也可以用来比较引用数据类型,比较的重点在两个对象的内存地址是否相等;equals 方法主要用来比较引用数据类型,一般我们都会重写,重写后的 equals 方法主要是比较两个对象的值是否相等。因此,两者的侧重点是不同的。
但我们在源码中得知,Object 类中的 equals 方法本质上是调用 == 运算符,我们想要实现对值的比较必须要对 equals 方法进行重写,我们可以参考一下 String 类是如何重写 equals 方法:
public boolean equals(Object anObject) {
// 如果内存地址相等,那必须equal
if (this == anObject) {
return true;
}
// 如果对象是String类型
if (anObject instanceof String) {
String anotherString = (String)anObject;
// 获取调用方的字符串长度赋值给n
int n = value.length;
// 判断长度相等
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// 那我们就逐个字符的比较
while (n-- != 0) {
// 从前往后,任意一个字符不匹配,直接返回false
if (v1[i] != v2[i])
return false;
i++;
}
// 全部匹配结束,返回true
return true;
}
}
return false;
}String 是引用类型,比较时不能比较引用是否相等,重点是字符串的内容是否相等。所以 String 类定义两个对象相等的标准是字符串内容都相同。
在Java规范中,对 equals 方法的使用必须遵循以下几个原则:
① 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
② 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
③ 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
④ 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改
⑤ 对于任何非空引用值 x,x.equals(null) 都应返回 false。
为何重写 equals 方法就得重写 hashCode 方法?
在 equals 源码注释上官方说明了,在重写 equals 方法时也要重写 hashCode 方法,目的是为了保证相同对象有相同的 hash 值。

为什么相同对象一定要有相同的 hash 值?
关于这个问题,我们要从它们的应用场景来说。在 Java 中有很多集合类用到了 hashCode 方法,如 HashMap,HashSet 等,它们的底层结构是一个哈希表,在存储数据的 key 值时,要先获取到对应的 hash 值,针对这个值进行一定的扰动,然后与数组的长度做一个运算,最终计算出对应的下标,根据这个下标把 key 和 value 封装成一个对象存到底层的数组。在整个过程中,我们需要用到 hashCode。
假设我们需要再次存储一个一样的对象,理论上来说,key 值相同我们需要对 value 值进行覆盖,那么在第二次添加时我们怎么知道这两个值是否相等?假如我们直接调用重写后的 equals 方法进行比较,由于重写后 equals 方法的内部逻辑可能非常复杂,这样效率是很低的。因此我们可以先利用 hash 值做对比,hash 值不相等时直接返回,在 hash 值相等的情况下再通过 equals 方法进行确认。
基于这种机制,假如我们重写了 equals 方法却没有重写 hashCode 方法,就会发生 equals 比较是相等的但 hash 值却不同的情况,这样数据就会混乱。
toString 方法
源码:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}getClass().getName()是返回对象的全类名(包含包名),Integer.toHexString(hashCode())是以16进制无符号整数形式返回此哈希码的字符串表示形式。打印某个对象时,默认是调用 toString 方法,比如
System.out.println(person),等价于System.out.println(person.toString())。
finalize 方法
源码:
protected void finalize() throws Throwable { }当 GC 确定不再有对该对象的引用时,GC 会调用对象的
finalize()方法来清除回收。Java VM 会确保一个对象的
finalize()方法只被调用一次,而且程序中不能直接调用finalize()方法。finalize()方法通常也不可预测,而且很危险,一般情况下,不必要覆盖finalize()方法。
registerNatives 方法
源码:
private static native void registerNatives();这是一个本地方法,我们要知道一个类定义了本地方法后,想要调用操作系统的实现,必须还要装载本地库。
static {
registerNatives();
}静态代码块就是一个类在初始化过程中必定会执行的内容,所以在类加载的时候是会执行该方法的,通过该方法来注册本地方法。
