Object类简介
在Java中,Object类位于java.lang包中,它是所有类的超类(父类)。这意味着所有Java类都直接或间接地继承自Object类。Object类提供了一些基本方法,这些方法对于所有对象都是通用的。
1. public final native Class<?> getClass()
此方法返回运行时类的Class
对象。Class
类的实例代表正在运行的Java应用程序中的类和接口。
String s = "Hello, World!";
Class<?> clazz = s.getClass();
System.out.println(clazz.getName()); // 输出 java.lang.String
在Java中,Object.getClass()
方法是一个非常重要的方法,它属于所有Java对象的基类 Object
的一部分。此方法返回调用它的对象的运行时类。这意呀着,无论你的对象是在编译时声明的什么类型,getClass()
方法都会返回对象实际所属的类(包括继承而来的类,但通常返回的是最具体的类)。这个方法的主要应用包括但不限于:
-
类型检查和转换:
在Java中,有时我们可能需要根据对象的实际类型来执行不同的操作。使用getClass()
方法可以帮助我们进行这种类型检查。虽然通常推荐使用instanceof
关键字进行类型检查,但在某些情况下,了解对象的确切类可能是必要的。Object obj = new String("Hello");
if (obj.getClass() == String.class) {
System.out.println("对象是String类型的");
}
注意:虽然上述代码可以工作,但通常推荐使用
instanceof
因为它更灵活,能处理继承的情况。 -
反射(Reflection):
getClass()
方法返回的对象Class
是Java反射机制的基础。通过这个对象,我们可以动态地获取类的信息(如字段、方法、构造函数等),并且可以动态地创建对象、调用方法等。Object obj = "Hello";
Class<?> clazz = obj.getClass();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
这段代码展示了如何获取一个对象的所有公共方法名。
-
日志记录和调试:
在开发过程中,了解对象的实际类型对于调试和记录日志非常有用。通过getClass().getName()
可以获取对象的完整类名,这有助于确定对象的确切类型。Object obj = new ArrayList<>();
System.out.println("对象的类型是: " + obj.getClass().getName());
-
工厂模式和泛型:
在一些设计模式中,如工厂模式,可能需要根据对象的类型来创建不同类型的对象。虽然这通常与Class
对象直接相关,但getClass()
方法可以在确定需要哪种类型时提供帮助。在泛型编程中,虽然直接使用
getClass()
来区分泛型类型(如List<String>
和List<Integer>
)是不可行的(因为泛型类型信息在运行时被擦除),但getClass()
方法可以用于获取具体对象(如列表中的元素)的类型信息。
总的来说,Object.getClass()
方法是Java中一个非常基础且强大的方法,它使得在运行时获取和操作对象类型信息成为可能。
2. public native int hashCode()
此方法返回对象的哈希码值,该值由对象的地址或者某些信息转换而来,用于在哈希表中快速查找对象。
String s = "Hello, World!";
System.out.println(s.hashCode()); // 输出一个整数,例如 1498789909
在Java中,equals()
方法和 hashCode()
方法之间有一个非常重要的约定,这个约定确保了基于哈希的集合(如 HashSet
、HashMap
等)能够正确地工作。这个约定是:
-
如果两个对象通过
equals(Object obj)
方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode()
方法都必须产生相同的整数结果。 -
如果两个对象通过
equals(Object obj)
方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode()
方法不强制要求产生不同的整数结果。但是,为不同的对象产生不同的整数结果可以提高哈希表的性能。
这个约定意味着,如果两个对象相等(即 equals()
方法返回 true
),那么它们的 hashCode()
值也必须相等。但是,反过来并不成立:即使两个对象的 hashCode()
值相等,这并不意味着这两个对象通过 equals()
方法比较也一定相等。这是因为 hashCode()
方法的设计目标是在尽可能减少碰撞(即不同的对象产生相同的哈希码)的同时,提高哈希表的性能。
例如,考虑一个简单的 Person
类,其中包含 firstName
和 lastName
两个字段。如果两个 Person
对象的 firstName
和 lastName
都相同,那么它们的 equals()
方法会返回 true
,并且它们的 hashCode()
方法也应该返回相同的值。但是,如果有两个 Person
对象,它们的 hashCode()
偶然地(或者由于设计上的某种巧合)产生了相同的值,但这并不意味着它们的 firstName
和 lastName
都相同,即 equals()
方法可能会返回 false
。
3. public boolean equals(Object obj)
用于比较两个对象是否相等。默认实现是比较两个对象的地址值,但通常我们会根据需要重写equals()
方法来进行逻辑比较。
String s = "Hello";
String a = new String("Hello");
String c = new String("Hello");
String b = "Hello";
System.out.println(s.equals(a)); // true
System.out.println(s.equals(b)); // true
System.out.println(s == a ); // flase
System.out.println(s == b ); // true
System.out.println(a.equals(c) ); // true
System.out.println(a == c ); // false
String
类的equals
方法,该方法比较的是字符串的内容,而不是对象的引用。,==比较的是内存地址。
4. protected native Object clone() throws CloneNotSupportedException
此方法用于创建并返回当前对象的一个副本。为了使用clone()
方法,类必须实现Cloneable
接口,否则会抛出CloneNotSupportedException
。
示例:
class Person implements Cloneable {
int age;
String name;
Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// ...
Person p1 = new Person(25, "John");
Person p2 = (Person) p1.clone();
System.out.println(p1 == p2); // 输出 false
System.out.println(p1.equals(p2)); // 输出 true,如果没有重写equals方法,这里会输出 false
在这个例子中,我们创建了Person
类的一个副本p2
。p1
和p2
是两个不同的对象,但它们的内容相同。
扩展:object.clone()
方法是深克隆还是浅克隆?
在Java中,Object.clone()
方法实现的是浅克隆(Shallow Clone)。这意味着,当你对一个对象调用 clone()
方法时,它会创建一个该对象的新实例,但是新实例中的非静态字段是原始对象对应字段的引用拷贝,而不是字段内容的深拷贝。
简单来说,如果原始对象中的字段是基本数据类型(如int, double等),那么这些字段在新对象中会有相同的值(因为基本数据类型是直接拷贝值)。但是,如果字段是对象类型(即引用类型),那么新对象中的这些字段仅仅是原始对象中相应字段的引用拷贝,它们指向内存中的同一个对象。
为了实现深克隆(Deep Clone),你需要:
- 实现
Cloneable
接口(虽然这个接口不包含任何方法,但它是一种约定,表明对象可以被克隆)。 - 重写
clone()
方法。在重写的clone()
方法中,除了调用super.clone()
来创建对象的浅拷贝外,还需要手动对对象中的每一个引用类型字段进行深拷贝(即创建新的对象实例,并复制原始对象的内容到新对象中)。
下面是一个简单的例子,说明如何实现深克隆:
class Person implements Cloneable {
int age;
String name;
Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// ...
Person p1 = new Person(25, "John");
Person p2 = (Person) p1.clone();
System.out.println(p1 == p2); // 输出 false
System.out.println(p1.equals(p2)); // 输出 true,如果没有重写equals方法,这里会输出 false
注意,上面的例子中,虽然 skills
列表在 clone()
方法中被重新创建,但列表中的元素(这里是字符串)仍然是原始字符串的引用,因为字符串在Java中是不可变的。如果列表中的元素是可变的对象,那么你可能需要进一步实现这些元素的深克隆。
注意:
- 浅拷贝只复制对象本身及其非静态成员变量,但不复制这些成员变量所引用的对象。
- 深拷贝则不仅复制对象本身,还复制对象所引用的所有对象。实现深拷贝通常需要编写更多的代码,特别是当对象图中存在循环引用时。
- 在使用
clone()
方法时,需要注意线程安全问题,因为clone()
方法并不保证线程安全。
5. public String toString()
返回对象的字符串表示形式。通常,我们会重写这个方法,以返回更有意义的信息。
举例说明:
class Person {
int age;
String name;
Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
// ...
Person p = new Person(25, "John");
System.out.println(p); // 输出 Person{name='John', age=25}
6. public final void wait() throws InterruptedException
此方法使当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或者超过指定的时间量。
扩展:
wait()
与sleep()
是Java中用于线程控制和调度的两个重要方法,但它们之间存在明显的区别。以下是它们之间的主要区别:
1. 所属类和方法类型
- wait():是
java.lang.Object
类的一个实例方法。这意味着所有Java对象都继承了wait()
方法。 - sleep():是
java.lang.Thread
类的一个静态方法。它用于控制当前线程的行为,而不是对象的行为。
2. 用途和原理
- wait():主要用于线程间的通信。当一个线程调用某个对象的
wait()
方法时,它会释放该对象的锁(如果当前线程持有该对象的锁),并进入等待状态,直到其他线程调用同一对象的notify()
或notifyAll()
方法来唤醒它。 - sleep():用于使当前线程暂停执行一段时间,让出CPU资源给其他线程。在指定的时间间隔后,线程会自动苏醒并继续执行。调用
sleep()
方法时,线程不会释放它持有的任何锁。
3. 锁的处理
- wait():会释放当前线程持有的对象锁(monitor lock),使得其他线程可以访问该对象并进行同步操作。
- sleep():不会释放当前线程持有的锁。如果线程在持有锁的情况下调用
sleep()
,那么在睡眠期间,其他线程无法获取该锁。
4. 使用场景
- wait():通常与
synchronized
关键字一起使用,在需要线程间通信或同步时非常有用。例如,生产者-消费者模型中就经常使用wait()
和notify()
或notifyAll()
来实现线程间的协作。 - sleep():主要用于控制线程的执行节奏,或者在需要暂停执行一段时间(如等待某个条件成立)时使用。但它不涉及线程间的通信。
5. 异常处理
- wait():在调用
wait()
方法时,不需要显式地捕获异常,因为它会抛出InterruptedException
,但这个异常通常是在线程等待过程中被中断时才会发生的。 - sleep():调用
sleep()
方法时必须处理InterruptedException
异常,因为这个异常可能会在睡眠期间被其他线程的中断操作触发。
6. 调用位置和条件
- wait():必须在
synchronized
块或synchronized
方法中调用,因为它要求线程必须持有对象的锁。 - sleep():可以在任何地方调用,不需要线程持有任何锁。
7. public final native void notify()
此方法唤醒在此对象监视器上等待的单个线程。如果有多个线程都在此对象上等待,则会选择唤醒其中一个线程。
扩展:
notify()
是 Java 中Object
类的一个方法,用于线程间的通信。以下是对notify()
方法的详细解释:一、基本概述
- 作用:唤醒正在等待该对象监视器(monitor)的某一个线程。
- 所属类:
java.lang.Object
- 使用场景:当多个线程需要协调执行顺序或共享资源时,
notify()
方法可以用来唤醒等待线程。二、工作原理
- 线程等待:当一个线程调用某个对象的
wait()
方法时,它会释放该对象的锁并进入等待状态,直到其他线程在该对象上调用notify()
或notifyAll()
方法。- 唤醒线程:
notify()
方法会随机唤醒在该对象上等待的线程之一(如果有多个线程在等待)。被唤醒的线程会尝试重新获取该对象的锁,以便继续执行。- 锁的处理:需要注意的是,
notify()
方法本身并不会立即释放对象锁。锁的释放发生在调用notify()
方法的线程退出其同步块或同步方法之后。三、使用注意事项
- 必须在同步块中调用:
wait()
、notify()
和notifyAll()
方法都必须在synchronized
方法或synchronized
块内部调用,以确保线程在调用这些方法时持有对象的锁。- 避免死锁:不当使用
wait()
和notify()
可能会导致死锁或活锁。因此,在编写多线程程序时需要谨慎设计同步策略。- 异常处理:
wait()
方法会抛出InterruptedException
异常,因此调用wait()
方法的代码需要正确处理这个异常。四、示例代码
public class WaitNotifyExample { private final Object lock = new Object(); private boolean ready = false; public void waitForReady() throws InterruptedException { synchronized (lock) { while (!ready) { lock.wait(); // 等待ready变为true } // 执行后续操作 System.out.println("Ready to proceed"); } } public void setReady() { synchronized (lock) { ready = true; lock.notify(); // 唤醒等待的线程 } } public static void main(String[] args) { WaitNotifyExample example = new WaitNotifyExample(); Thread waiter = new Thread(() -> { try { example.waitForReady(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); Thread notifier = new Thread(() -> { try { // 模拟一些准备工作 Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } example.setReady(); }); waiter.start(); notifier.start(); } }
以下是一个简单的示例,展示了如何使用
notify()
方法:在这个示例中,waitForReady()
方法会等待ready
变量变为true
,而setReady()
方法会将ready
变量设置为true
并唤醒等待的线程。注意,这两个方法都使用了synchronized
块来确保线程安全,并在同步块内部调用了wait()
和notify()
方法。
8. public final native void notifyAll()
此方法唤醒在此对象监视器上等待的所有线程。
wait(), notify(), 和 notifyAll()方法是与对象监视器相关的方法,它们用于实现线程间的同步。
notifyAll()
是 Java 中Object
类的一个方法,用于多线程编程中的线程通信。以下是关于notifyAll()
方法的详细解释:一、基本概述
- 作用:唤醒正在等待该对象监视器(monitor)上的所有线程。
- 所属类:
java.lang.Object
- 使用场景:当多个线程因为某个条件未满足而等待时,一旦条件满足,可以通过
notifyAll()
方法唤醒所有等待的线程,使它们重新竞争锁并进入就绪状态。二、工作原理
- 线程等待:当一个或多个线程调用某个对象的
wait()
方法时,它们会释放该对象的锁并进入等待状态,直到其他线程在该对象上调用notify()
、notifyAll()
方法,或者等待超时。- 唤醒线程:
notifyAll()
方法会唤醒在该对象上等待的所有线程。被唤醒的线程会进入对象的同步队列,等待获取对象的锁之后再次进入运行状态。- 锁的处理:需要注意的是,
notifyAll()
方法本身并不会立即释放对象锁。锁的释放发生在调用notifyAll()
方法的线程退出其同步块或同步方法之后。三、使用注意事项
- 必须在同步块中调用:
wait()
、notify()
和notifyAll()
方法都必须在synchronized
方法或synchronized
块内部调用,以确保线程在调用这些方法时持有对象的锁。- 避免虚假唤醒:虽然
notifyAll()
会唤醒所有等待的线程,但有时候线程被唤醒后可能会发现条件仍未满足(即发生了“虚假唤醒”),因此通常需要在循环中检查条件。- 合理设计同步策略:不当使用
wait()
、notify()
和notifyAll()
可能会导致死锁或活锁。因此,在编写多线程程序时需要谨慎设计同步策略。四、示例代码
以下是一个简单的示例,展示了如何使用
notifyAll()
方法:public class WaitNotifyAllExample { private final Object lock = new Object(); private boolean flag = false; public void waitForFlagChange() throws InterruptedException { synchronized (lock) { while (!flag) { lock.wait(); // 等待flag变为true } // 执行后续操作 System.out.println("Flag is true, proceeding..."); } } public void changeFlagAndNotifyAll() { synchronized (lock) { flag = true; lock.notifyAll(); // 唤醒所有等待的线程 } } public static void main(String[] args) { WaitNotifyAllExample example = new WaitNotifyAllExample(); // 创建并启动多个等待线程 for (int i = 0; i < 5; i++) { new Thread(() -> { try { example.waitForFlagChange(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); } // 在某个时刻改变标志并唤醒所有线程 try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } example.changeFlagAndNotifyAll(); } }
在这个示例中,
waitForFlagChange()
方法会等待flag
变量变为true
,而changeFlagAndNotifyAll()
方法会将flag
变量设置为true
并唤醒所有等待的线程。注意,这里使用了synchronized
块来确保线程安全,并在同步块内部调用了wait()
和notifyAll()
方法。
9. public final void wait(long timeout) throws InterruptedException
使当前线程等待直到另一个线程调用notify()或notifyAll()方法,或者指定的时间已过。
10. public final void wait(long timeout, int nanos) throws InterruptedException
与上一个方法类似,但它允许更精细的控制等待时间,包括纳秒级别。
11. protected void finalize() throws Throwable
此方法由垃圾回收器在确定没有更多的引用指向对象时调用。它用于在对象被销毁前进行清理工作。
finalize()
是 Java 中Object
类的一个方法,用于在垃圾收集器决定销毁对象之前,由垃圾收集器调用此方法。它的主要目的是让对象在被销毁前有机会进行清理工作,比如释放非内存资源(如文件句柄、网络连接等)。然而,需要注意的是,从 Java 9 开始,finalize()
方法已被标记为过时(deprecated),并且在未来的版本中可能会被移除。基本概述
- 作用:在对象被垃圾收集器销毁之前执行清理工作。
- 所属类:
java.lang.Object
- 调用时机:由垃圾收集器在销毁对象之前调用,但具体何时调用是不确定的,也无法保证一定会被调用。
使用注意事项
- 不推荐使用:由于
finalize()
方法的不确定性和性能影响,以及从 Java 9 开始被标记为过时,因此不建议在新开发的应用中使用它。- 清理资源:如果需要清理非内存资源,建议使用
try-with-resources
语句(对于实现了AutoCloseable
或Closeable
接口的资源)或显式地在finally
块中关闭资源。- 性能问题:
finalize()
方法的调用会显著影响垃圾收集的性能,因为它增加了垃圾收集器的复杂度。- 安全性问题:
finalize()
方法可以被子类覆盖,这可能会导致不可预测的行为,因为垃圾收集器可能会调用子类覆盖的finalize()
方法,而不是父类中的方法。替代方案
- 显式清理:在对象不再需要时,显式地调用清理方法来释放资源。
- 使用
try-with-resources
:对于实现了AutoCloseable
或Closeable
接口的资源,可以使用try-with-resources
语句来自动管理资源的关闭。- 使用
PhantomReference
:在 Java 中,PhantomReference
与引用队列(ReferenceQueue
)结合使用,可以在对象被回收时得到通知,但这种方式不会阻止对象的回收,也不能用于直接调用finalize()
方法。示例(不推荐)
尽管不推荐使用
finalize()
,但以下是一个简单的示例,展示了其基本用法:public class FinalizeExample { @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("Finalize method called."); // 在这里执行清理工作 } public static void main(String[] args) { FinalizeExample example = new FinalizeExample(); // 在某些情况下,example 对象可能会被垃圾收集器回收,并调用其 finalize() 方法 // 但请注意,这不是一个可靠的清理资源的方式 example = null; // 注意:这里不会立即触发垃圾收集,也不会立即调用 finalize() // 为了演示,可以显式调用 System.gc(),但这并不保证 finalize() 会被调用 // System.gc(); // 不推荐在生产代码中调用 } }
请注意,由于垃圾收集器的行为是不确定的,因此即使将
example
设置为null
并显式调用System.gc()
,也不能保证finalize()
方法会被调用。此外,显式调用System.gc()
是一个糟糕的做法,因为它会干扰垃圾收集器的正常工作,并可能导致性能问题。