深度剖析JDK 8中Object:源码级解读与实战探秘
一、Object类概述
在Java编程中,Object
类是所有类的根父类,无论是否显式声明,所有的Java类都直接或间接地继承自java.lang.Object
类。这意味着每个Java对象都继承了Object
类的方法,这些方法为所有对象提供了一组基本的行为和特征,保证了Java语言的统一性和一致性。由于Object
类是所有类的基类,所以在使用java.lang.Object
包下的类时不需要导入包。
(一)Object类的作用
- 统一规范:为所有Java对象提供了一组基本方法,使得任何Java对象都能使用这些方法,保证了Java语言的统一性和一致性。
- 多态支持:
Object
类中的方法如equals()
、hashCode()
和toString()
确保了Java中的所有对象都具备基本的行为特征,这为多态性、封装性和继承性提供了基础。 - 基础方法:定义了一些基础操作,比如对象比较、克隆、线程调度相关的
wait/notify
等方法。
(二)Object类的构造方法
Object
类没有显示的构造方法,只有编译器默认提供的无参构造方法。如果构造函数内没有任何操作,那么该方法会省略不写,如Object
类;如果有特殊处理,则会手动写出来,如ArrayList
。
(三)Object类的字段
Object
类没有定义字段。
二、JDK 8 Object源码分析
(一)类定义
public class Object {
// 静态代码块及本地方法注册
private static native void registerNatives();
static {
registerNatives();
}
// 返回当前对象所属的类的类对象
public final native Class<?> getClass();
// 返回当前对象的哈希码
public native int hashCode();
// 判断两个对象是否等价
public boolean equals(Object obj) {
return (this == obj);
}
// 浅拷贝,使用时往往需要重写为 public 形式
// 注意: 要求被克隆的对象所属的类实现 Cloneable 接口
protected native Object clone() throws CloneNotSupportedException;
// 返回一个表示这个对象的字符串
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
// 激活正在等待此对象监视器的单个线程
public final native void notify();
// 唤醒正在等待此对象监视器的所有线程
public final native void notifyAll();
// 使当前线程等待,直到另一个线程为此对象调用notify方法或notifyall方法,或者指定的时间已过
public final native void wait(long timeout) throws InterruptedException;
// 使当前线程等待指定的毫秒数和纳秒数
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException("nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
// 使当前线程等待,直到另一个线程为此对象调用notify方法或notifyall方法
public final void wait() throws InterruptedException {
wait(0);
}
// 在对象被垃圾回收器回收之前会调用这个方法(不推荐使用)
protected void finalize() throws Throwable { }
}
(二)方法详解
1. 静态代码块及本地方法注册
private static native void registerNatives();
static {
registerNatives();
}
- 关键字含义:关键字
native
表示这个方法是通过本地代码(如C/C++)实现的接口,这些代码直接与底层系统的资源或系统API交互。registerNatives()
方法的主要作用是在内存中注册由本地方法实现的一些关键函数,这些函数通常是性能敏感的,直接与操作系统层交互。这样做可以提高方法调用的效率,避免每次调用时都进行查找和链接过程。 - 静态初始化块:在类被Java虚拟机加载时执行,且只执行一次,静态代码块常用于执行类级别的初始化代码。代码块中调用了
registerNatives()
方法来初始化与对象操作相关的本地方法,确保这些方法在被Java代码调用前已正确链接。
2. getClass()
方法
public final native Class<?> getClass();
- 功能描述:返回当前对象所属的类的类对象。这里的
Class<?>
表示方法返回一个Class
类型的对象,其中?
是一个通配符,表示任何类型。在实际使用中,这个返回类型会更具体地表达为Class<? extends |X|>
,这里的|X|
表示调用getClass()
的对象的类型。通过这个返回的Class
对象,可以访问关于类的各种信息,如类的名字、包含的方法、实现的接口等。 - 示例代码:
Number n = 0;
Class<? extends Number> c = n.getClass();
在上述代码中,不需要进行强制类型转换,因为getClass()
方法返回的类型已经是具体的类型。
3. hashCode()
方法
public native int hashCode();
- 功能描述:
hashCode()
方法的主要功能是为对象提供一个哈希码值,这个值主要被用于支持哈希表的使用,例如在Java集合框架中广泛使用的HashMap
。哈希码是根据对象的内部状态计算出的一个整数,用于确定对象在哈希表中的位置,以实现快速的查找、插入和删除操作。 - 约定和规则:
- 重复调用一致性:对同一个对象多次调用
hashCode()
方法,必须始终返回相同的整数,前提是对象用于equals
比较的信息没有被修改。 - 等价对象的哈希码相等:如果两个对象通过
equals(Object)
方法判断相等,那么这两个对象调用hashCode()
必须返回相同的整数值。 - 不等对象的哈希码优化:为了提高哈希表的性能,最好为不同的对象生成不同的哈希码,减少哈希碰撞,从而优化数据结构的性能。
- 重复调用一致性:对同一个对象多次调用
4. equals(Object obj)
方法
public boolean equals(Object obj) {
return (this == obj);
}
- 功能描述:
equals()
方法用于确定调用对象与作为参数传递的对象(obj
)是否 “相等”。在Object
类的实现中,仅当两个对象引用指向同一内存地址时,才认为它们相等。 - 等价关系规则:
- 自反性:对于任何非空引用值
x
,x.equals(x)
必须返回true
。 - 对称性:对于任何非空引用值
x
和y
,x.equals(y)
应当仅在y.equals(x)
也返回true
时返回true
。 - 传递性:如果
x.equals(y)
和y.equals(z)
都返回true
,则x.equals(z)
也应返回true
。 - 一致性:只要对象
x
和y
的等价比较中用到的信息没有改变,反复调用x.equals(y)
应当始终返回同样的结果。 - 对
null
的比较:对于任何非空引用值x
,x.equals(null)
应当返回false
。
- 自反性:对于任何非空引用值
- 实现注意事项:在自定义类中重写
equals()
方法时,应确保遵守上述等价关系的规则。当重写equals()
方法时,也应该重写hashCode()
方法 ,以保持hashCode()
的一般约定,即相等的对象必须具有相等的哈希码。在Java的集合框架中,例如HashMap
和HashSet
,对象是否相等直接影响到对象的存储和检索,正确实现equals()
方法是使用这些集合的前提。
5. clone()
方法
protected native Object clone() throws CloneNotSupportedException;
- 功能描述:
clone()
方法的目的是创建一个新的对象,这个新对象在逻辑上与原始对象相等,但在内存中占用不同的位置(即x.clone() != x
总是为真)。它实现了对象的浅拷贝,即拷贝对象及其非静态字段,但不递归复制字段指向的对象。 - 浅拷贝VS深拷贝:
- 浅拷贝:默认的
clone()
方法实现是浅拷贝,意味着对象的非对象字段(如基本数据类型字段)会被完全复制,但对象字段仅复制引用,不复制引用的对象本身。 - 深拷贝:需要手动实现,在
clone()
方法中逐个复制对象内部所有可变的引用类型字段。通常涉及到修改super.clone()
返回的对象的一个或多个字段,确保所有内部的复杂结构都得到适当的复制。
- 浅拷贝:默认的
- 使用限制:Java要求任何使用
clone()
方法的类必须实现Cloneable
接口。这个接口是一个标记接口,不包含任何方法,其目的是表明类的设计者已经考虑到了复制问题,并且该类支持进行字段内容的复制。如果一个类没有实现Cloneable
接口而调用clone()
方法,将抛出CloneNotSupportedException
异常。所有的数组类型都被视为实现了Cloneable
接口,因此数组总是可以被克隆,数组类型的clone()
方法返回的类与原数组类型相同。
6. toString()
方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
- 功能描述:返回一个表示这个对象的字符串。该字符串由类全名 +
@
+ 对象的哈希值的无符号十六进制组成。在使用println
方法打印对象时会默认调用这个方法。在实际开发中,通常会重写该方法以提供有意义的字符串描述。
7. notify()
方法
public final native void notify();
- 功能描述:激活正在等待此对象监视器的单个线程。如果有线程正在等待这个对象,那么会选择唤醒其中一个线程。选择是任意的,并由实施者自行决定。线程通过调用其中一个
wait
方法来等待对象的监视器。 - 调用条件:此方法只能由该对象监视器的所有者线程调用。线程通过以下三种方式之一成为对象监视器的所有者:
- 通过执行该对象的同步实例方法。
- 通过执行在对象上同步的
synchronized
语句体。 - 对于
Class
类型的对象,通过执行该类的同步静态方法。
- 注意事项:在当前线程放弃此对象上的锁之前,唤醒的线程将无法继续。唤醒线程将以通常的方式与任何其他可能在该对象上为同步而主动竞争的线程竞争;例如,唤醒线程在成为下一个锁定该对象的线程时不享有可靠的特权或劣势。
8. notifyAll()
方法
public final native void notifyAll();
- 功能描述:唤醒正在等待此对象监视器的所有线程。线程通过调用其中一个
wait
方法在对象的监视器上等待。 - 调用条件和注意事项:与
notify()
方法相同,此方法只能由该对象监视器的所有者线程调用。在当前线程放弃此对象上的锁之前,唤醒的线程将无法继续。唤醒的线程将以通常的方式与任何其他可能在该对象上为同步而主动竞争的线程竞争。
9. wait(long timeout)
方法
public final native void wait(long timeout) throws InterruptedException;
- 功能描述:使当前线程等待,直到另一个线程为此对象调用
notify
方法或notifyall
方法,或者指定的时间已过。当前线程必须拥有此对象的监视器。 - 线程状态变化:此方法使当前线程将自身置于此对象的等待集中,然后放弃此对象上的所有同步声明。出于线程调度目的,线程将被禁用,并处于休眠状态,直到发生以下四种情况之一:
- 其他一些线程调用此对象的
notify
方法,而线程恰好被任意选择为要唤醒的线程。 - 其他一些线程为此对象调用
notifyall
方法。 - 其它一些线程
interrupt
方法中断当前线程。 - 指定的实时时间已过,或多或少。但是,如果超时为零,则不考虑实时性,线程只需等待通知。
- 其他一些线程调用此对象的
- 假唤醒问题:线程也可以在不被通知、中断或超时的情况下唤醒,这就是所谓的假唤醒。虽然在实践中很少发生这种情况,但是应用程序必须通过测试导致线程被唤醒的条件来防范这种情况,并且如果条件不满足,继续等待。换句话说,等待应该总是以循环的形式出现。
10. wait(long timeout, int nanos)
方法
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException("nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
- 功能描述:使当前线程等待指定的毫秒数和纳秒数。该方法内部调用的是
wait(long timeout)
方法,如果nanos
大于0,则将timeout
加1。
11. wait()
方法
public final void wait() throws InterruptedException {
wait(0);
}
- 功能描述:使当前线程等待,直到另一个线程为此对象调用
notify
方法或notifyall
方法。该方法调用的是wait(long timeout)
方法,将超时时间设置为0,表示无限等待。
12. finalize()
方法
protected void finalize() throws Throwable { }
- 功能描述:在对象被垃圾回收器回收之前会调用这个方法。但由于其执行时间的不确定性,现在已经不推荐使用该方法来释放资源。该方法在
Object
类中的实现为空,子类可以重写该方法来实现特定的清理操作。
三、Object类与其他相关类和工具类的比较
(一)Object类与Objects类
java.util.Objects
类是一个工具类,提供了一系列静态方法,用于对对象进行操作,避免了空指针异常。例如,Objects.equals()
方法可以安全地比较两个对象是否相等,会先判断对象是否为null
,而Object
类的equals()
方法在比较null
对象时会抛出NullPointerException
。示例代码如下:
import java.util.Objects;
public class MyClass {
private String name;
public boolean isNameEqual(String otherName) {
// 使用Objects类的equals静态方法进行null安全比较
return Objects.equals(this.name, otherName);
}
}
(二)Object类与String类
String
类重写了Object
类的equals()
方法,用于比较两个字符串的内容是否相等。它会逐字符地检查两个字符串的值是否相同,对null
值的比较将直接抛出NullPointerException
。而Object
类的equals()
方法默认比较两个对象的引用地址是否相等。示例代码如下:
String s1 = "hello";
String s2 = new String("hello");
System.out.println(s1.equals(s2)); // 输出 true,比较内容
System.out.println(s1 == s2); // 输出 false,比较引用地址
四、使用Object类的注意事项
- 类型转换:如果频繁地使用
Object
类型的变量来存储不同类型的对象,可能会遇到类型转换的问题。这时,需要小心处理ClassCastException
异常。例如:
Object obj = "hello";
Integer num = (Integer) obj; // 会抛出ClassCastException异常
- 方法重写:
Object
类的一些方法,如equals()
和hashCode()
,如果不重写,可能会导致意外的行为。因此,在使用Object
类时,需要对这些细节有充分的了解。例如,在自定义类中,如果需要比较对象的内容是否相等,就需要重写equals()
方法,并且为了保证在哈希集合中正常使用,还需要重写hashCode()
方法。 finalize()
方法的使用:由于finalize()
方法的执行时间不确定,不推荐使用该方法来释放资源。可以使用try-with-resources
语句或finally
块来确保资源的正确释放。
综上所述,Object
类在Java中扮演着至关重要的角色。它不仅是所有类的根基,还提供了一系列基础方法,使得我们能够更灵活地处理对象。然而,使用Object
类时也需要注意一些细节,以避免潜在的问题。在编程过程中,理解并正确使用Object
类可以大大提高代码的灵活性和可维护性。