在 Java 中,Object类占据着独特地位,它是所有 Java 类的根类。这意味着,Java 中的每一个类,无论是否被开发者显式声明,都默认继承自Object类。这种设计赋予了 Java 语言高度的统一性和强大的通用性,使得所有对象都能共享Object类提供的基础特性。
Object 类提供的方法
registerNatives()
registerNatives()是Object类中的一个私有静态方法,它的作用是注册本地方法。Java 通过 JNI(Java Native Interface)机制允许 Java 代码调用本地 C 或 C++ 代码,以实现一些 Java 无法直接完成的功能,比如访问底层系统资源、提高性能敏感代码的执行效率等。registerNatives方法就是在这个过程中扮演关键角色,它负责将 Java 方法和对应的本地实现进行关联。当 Java 虚拟机加载Object类时,会调用registerNatives方法,将Object类中定义的本地方法(如hashCode、equals等方法的一些底层实现可能依赖本地代码)与实际的本地函数进行注册绑定。这样,在运行时,当这些本地方法被调用时,Java 虚拟机就能够准确地找到并执行对应的本地代码。
equals(Object obj)
equals(Object obj)方法用于判断两个对象在逻辑上是否相等。在 Object 类的原始定义中,它仅仅比较两个对象的内存地址,即判断两个引用是否指向同一对象。然而,在实际的编程场景中,我们常常需要根据对象的属性值来判断相等性,因此,在自定义类中,重写equals方法是极为常见的操作。例如,在一个表示用户信息的类中,可能需要根据用户名和密码来判断两个用户对象是否相等,而不仅仅是内存地址。重写equals方法时,需要遵循自反性、对称性、传递性和一致性的原则,以确保逻辑的正确性。
public boolean equals(Object obj) {
return (this == obj);
}
hashCode()
hashCode()方法返回对象的哈希码值,这个值在哈希表等数据结构中扮演着重要角色,用于快速定位和比较对象。一般来说,如果两个对象通过equals方法判断为相等,那么它们的哈希码值也应该相等。不过,不同的对象也可能具有相同的哈希码值,这就是哈希冲突。在实际应用中,合理地重写hashCode方法可以提高哈希表的性能,减少哈希冲突的发生。例如,在将自定义对象作为键值存入HashMap时,正确的哈希码计算能确保对象的快速查找和存储。
/**
*返回对象的哈希码值。这种方法是
*支持哈希表,如
*{@linkjava.util.HashMap}。
p
*{@code hashCode}的通用协议是:
*<ul>
*<li>每当在同一对象上多次调用它时
*Java应用程序的执行,{@code hashCode}方法
*必须始终返回相同的整数,前提是没有信息
*在对象的{@code equals}比较中使用的方法已被修改。
*此整数不需要在一次执行中保持一致
*应用程序到同一应用程序的另一个执行。
*<li>如果根据{@code equals(Object)}两个对象相等
*方法,然后在每个对象上调用{@code hashCode}方法
*这两个对象必须产生相同的整数结果。
*<li>如果两个对象不相等,则<em>不要求</em>
*根据{@linkjava.lang.Object#equals(java.lang.Object)}
*方法,然后在每个
*两个对象必须产生不同的整数结果。然而
*程序员应该意识到,产生不同的整数结果
*对于不等对象,可以提高哈希表的性能。
*</ul>
p
*在合理可行的情况下,hashCode方法由
*类{@code Object}确实为不同的对象返回不同的整数
*物体。(这通常是通过转换内部
*将对象的地址转换为整数,但此实现
*技术不是必需的
*Java与贸易;编程语言。)
*
*@返回此对象的哈希码值。
*@参见java.lang.Object#等于(java.lang.Object)
*@参见java.lang.System#identityHashCode
*/
public native int hashCode();
toString()
toString()方法返回对象的字符串表示形式,当我们尝试打印一个对象或者将其转换为字符串时,这个方法会自动被调用。在 Object 类中,toString方法的默认实现返回的是类名和对象的内存地址,这对于开发者理解对象的实际内容帮助有限。因此,在自定义类中,通常会重写toString方法,以返回更有意义的信息,比如对象的属性值。例如,在一个表示商品的类中,重写toString方法可以返回商品的名称、价格和描述等信息,方便调试和输出。
getClass()
getClass()方法返回一个对象的运行时类,它返回的是一个Class对象,通过这个对象,我们可以获取到类的各种信息,如类名、包名、父类、接口、方法和字段等。这在反射机制中尤为重要,反射允许我们在运行时动态地获取和操作类的信息,实现诸如动态创建对象、调用方法和访问字段等功能。例如,通过getClass()方法获取到类的Class对象后,可以使用反射机制创建该类的实例,或者调用其私有方法。
/**
*返回此{@code Object}的运行时类。返回
*{@code Class}对象是由{@code锁定的对象
*所表示类的静态同步方法。
*
*<p><b>实际结果类型是{@code Class<?extends|X|>}
*其中{@code|X|}是静态类型的擦除
*调用{@code getClass}的表达式</b> For
*例如,此代码片段中不需要强制转换:</p>
*
p
*{@代码n=0;}<br>
*{@code Class<?extends Number>c=n.getClass();}
p
*
*@return表示运行时的{@code Class}对象
*这个对象的类。
*@jls 15.8.2类文字
*/
public final native Class<?> getClass();
wait()、notify()、notifyAll()
这三个方法用于线程间的同步和通信。wait()方法会使当前线程进入等待状态,直到其他线程调用该对象的notify()方法唤醒单个等待线程,或者调用notifyAll()方法唤醒所有等待线程。这些方法通常与synchronized关键字配合使用,用于实现线程之间的协作和同步。例如,在生产者 - 消费者模型中,生产者线程在生产数据后可以调用notify()方法通知消费者线程,而消费者线程在没有数据时可以调用wait()方法等待生产者线程的通知。
clone()
clone()方法用于创建并返回当前对象的一个副本。需要注意的是,Object 类中的clone方法是一个受保护的方法,并且默认实现是浅拷贝,即只复制对象的基本类型字段和对象引用,而不会递归地复制引用对象的内容。如果需要实现深拷贝,即复制对象及其所有引用对象的内容,需要在子类中重写clone方法,并手动处理对象引用的复制。例如,在一个包含复杂对象引用的自定义类中,重写clone方法时需要对每个引用对象也进行克隆,以确保副本的独立性。
/**
*创建并返回此对象的副本。确切含义
*“复制”的类型可能取决于对象的类别。将军
*意图是,对于任何对象{@code x},表达式:
*<blockquote>
*<上一页>
*x.clone()!=x</pre></blockquote>
*将为真,并且表达式为:
*<blockquote>
*<上一页>
*x.clone().getClass()==x.getClass()</pre></blockquote>
*将为{@code true},但这些不是绝对要求。
*虽然通常的情况是:
*<blockquote>
*<上一页>
*x.clone()等于(x)</pre></blockquote>
*将为{@code true},这不是绝对要求。
p
*按照惯例,返回的对象应该通过调用
*{@code super.clone}。如果一个类及其所有超类(除了
*{@code Object})遵守此约定,情况如下
*{@code x.clone().getClass()==x.getClass()}。
p
*按照惯例,此方法返回的对象应该是独立的
*这个对象(正在被克隆)。为了实现这种独立性,
*可能需要修改返回对象的一个或多个字段
*在返回之前,请使用{@code super.clone}。通常,这意味着
*复制构成内部“深层结构”的任何可变对象
*复制对象并替换对这些对象的引用
*对象与副本的引用。如果一个类只包含
*原始字段或对不可变对象的引用,那么它通常是
*{@code super.clone}返回的对象中没有字段的情况
*需要修改。
p
*类{@code Object}的方法{@code clone}执行
*特定的克隆操作。首先,如果此对象的类
*不实现接口{@code Cloneable},然后
*{@code CloneNotSupportedException}被抛出。请注意,所有数组
*被认为实现了接口{@code Cloneable},并且
*数组类型{@code T[]}的{@code clone}方法的返回类型
*是{@code T[]},其中T是任何引用或基元类型。
*否则,此方法将创建this类的新实例
*对象,并用以下内容初始化其所有字段
*该对象的相应字段,就像通过赋值一样;这个
*字段的内容本身不会被克隆。因此,这种方法
*执行此对象的“浅拷贝”,而不是“深拷贝”操作。
p
*类{@code Object}本身不实现接口
*{@code Cloneable},因此对对象调用{@code clone}方法
*其类为{@code Object}将导致抛出
*运行时出现异常。
*
*@返回此实例的克隆。
*@如果对象的类不存在,则抛出CloneNotSupportedException
*支持{@code Cloneable}接口。子类
*重写{@code clone}方法也可以
*抛出此异常,表示实例不能
*被克隆。
*@参见java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
finalize()
finalize()方法在垃圾回收器确定对象不再被引用,即将被回收时调用。在这个方法中,可以进行一些资源清理的操作,如关闭文件、释放数据库连接等。然而,在现代 Java 编程中,finalize()方法并不被推荐使用 ,主要有以下几方面原因:
- 垃圾回收时机不确定:垃圾回收器的运行时机是由 JVM 自行决定的,不受开发者控制。这就导致finalize()方法的执行时间无法预测,可能在对象不再被引用后的很长时间才会执行,这对于一些对资源释放及时性要求较高的场景来说是不可接受的。比如在数据库连接管理中,如果对象持有数据库连接,在对象不再使用时,希望能及时关闭连接以释放资源,而使用finalize()方法无法保证这一点。
- 性能影响:垃圾回收本身就是一个复杂且耗费资源的过程,在finalize()方法中执行额外的清理操作会进一步加重垃圾回收的负担,影响系统的整体性能。每次垃圾回收时,JVM 都需要花费额外的时间来执行finalize()方法中的代码,这可能导致垃圾回收的停顿时间变长,影响应用程序的响应性。
- 资源管理风险:由于finalize()方法执行的不确定性,可能会导致资源泄漏。如果在finalize()方法中发生异常,而没有正确处理,那么资源可能无法被正确释放,从而造成资源的浪费。相比之下,使用try - finally块或者AutoCloseable接口能够更可靠地管理资源,确保资源在不再使用时及时被释放。
/**
*垃圾回收时由垃圾回收器对对象调用
*确定不再有对该对象的引用。
*子类重写{@code finalize}方法来处理
*系统资源或执行其他清理。
p
*{@code finalize}的一般约定是它被调用
*如果Java交易;虚拟
*机器已确定不再有任何
*通过这种方式,任何具有以下特征的线程都可以访问此对象
*尚未死亡,除非是由于
*已准备就绪的其他对象或类的最终确定
*最终确定。{@code finalize}方法可以执行任何操作,包括
*使该对象再次可用于其他线程;通常的目的
*然而,{@code finalize}的任务是在执行清理操作之前
*该对象被不可撤销地丢弃。例如,finalize方法
*对于表示可能执行的输入/输出连接的对象
*显式I/O事务,在对象被访问之前断开连接
*永久丢弃。
p
*类{@code Object}的{@code finalize}方法不执行任何操作
*特别行动;它只是正常返回。子类
*{@code对象}可以覆盖此定义。
p
*Java编程语言不保证哪个线程会
*为任何给定的对象调用{@code finalize}方法。它是
*但是,保证调用finalize的线程不会
*在完成时持有任何用户可见的同步锁
*调用。如果finalize方法抛出未捕获的异常,
*该异常将被忽略,该对象的最终确定将终止。
p
*在为对象调用{@code finalize}方法后,没有
*采取进一步行动,直到Java虚拟机再次
*确定该对象不再有任何方法可以
*被任何尚未死亡的线程访问,包括可能的
*其他对象或类准备完成的动作,
*此时可以丢弃该对象。
p
*Java永远不会调用{@code finalize}方法多次
*任何给定对象的虚拟机。
p
*{@code finalize}方法抛出的任何异常都会导致
*此对象的最终确定将被暂停,但除此之外
*忽略。
*
*@throws抛出此方法引发的{@code Exception}异常
*@参见java.lang.ref.eakReference
*@参见java.lang.ref.hantomReference
*@jls 12.6类实例的最终确定
*/
protected void finalize() throws Throwable { }