根父类Object下的方法详解

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() 方法都会返回对象实际所属的类(包括继承而来的类,但通常返回的是最具体的类)。这个方法的主要应用包括但不限于:

  1. 类型检查和转换
    在Java中,有时我们可能需要根据对象的实际类型来执行不同的操作。使用 getClass() 方法可以帮助我们进行这种类型检查。虽然通常推荐使用 instanceof 关键字进行类型检查,但在某些情况下,了解对象的确切类可能是必要的。

    Object obj = new String("Hello");
    if (obj.getClass() == String.class) {
    System.out.println("对象是String类型的");
    }

    注意:虽然上述代码可以工作,但通常推荐使用 instanceof 因为它更灵活,能处理继承的情况。

  2. 反射(Reflection)
    getClass() 方法返回的对象 Class 是Java反射机制的基础。通过这个对象,我们可以动态地获取类的信息(如字段、方法、构造函数等),并且可以动态地创建对象、调用方法等。

    Object obj = "Hello";
    Class<?> clazz = obj.getClass();
    Method[] methods = clazz.getMethods();
    for (Method method : methods) {
    System.out.println(method.getName());
    }

    这段代码展示了如何获取一个对象的所有公共方法名。

  3. 日志记录和调试
    在开发过程中,了解对象的实际类型对于调试和记录日志非常有用。通过 getClass().getName() 可以获取对象的完整类名,这有助于确定对象的确切类型。

    Object obj = new ArrayList<>();
    System.out.println("对象的类型是: " + obj.getClass().getName());
  4. 工厂模式和泛型
    在一些设计模式中,如工厂模式,可能需要根据对象的类型来创建不同类型的对象。虽然这通常与 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() 方法之间有一个非常重要的约定,这个约定确保了基于哈希的集合(如 HashSetHashMap 等)能够正确地工作。这个约定是:

  1. 如果两个对象通过 equals(Object obj) 方法比较是相等的,那么调用这两个对象中任意一个对象的 hashCode() 方法都必须产生相同的整数结果。

  2. 如果两个对象通过 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类的一个副本p2p1p2是两个不同的对象,但它们的内容相同。

扩展:object.clone() 方法是深克隆还是浅克隆?

在Java中,Object.clone() 方法实现的是浅克隆(Shallow Clone)。这意味着,当你对一个对象调用 clone() 方法时,它会创建一个该对象的新实例,但是新实例中的非静态字段是原始对象对应字段的引用拷贝,而不是字段内容的深拷贝。

简单来说,如果原始对象中的字段是基本数据类型(如int, double等),那么这些字段在新对象中会有相同的值(因为基本数据类型是直接拷贝值)。但是,如果字段是对象类型(即引用类型),那么新对象中的这些字段仅仅是原始对象中相应字段的引用拷贝,它们指向内存中的同一个对象。

为了实现深克隆(Deep Clone),你需要:

  1. 实现 Cloneable 接口(虽然这个接口不包含任何方法,但它是一种约定,表明对象可以被克隆)。
  2. 重写 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() 方法可以用来唤醒等待线程。

二、工作原理

  1. 线程等待:当一个线程调用某个对象的 wait() 方法时,它会释放该对象的锁并进入等待状态,直到其他线程在该对象上调用 notify() 或 notifyAll() 方法。
  2. 唤醒线程notify() 方法会随机唤醒在该对象上等待的线程之一(如果有多个线程在等待)。被唤醒的线程会尝试重新获取该对象的锁,以便继续执行。
  3. 锁的处理:需要注意的是,notify() 方法本身并不会立即释放对象锁。锁的释放发生在调用 notify() 方法的线程退出其同步块或同步方法之后。

三、使用注意事项

  1. 必须在同步块中调用wait()notify() 和 notifyAll() 方法都必须在 synchronized 方法或 synchronized 块内部调用,以确保线程在调用这些方法时持有对象的锁。
  2. 避免死锁:不当使用 wait() 和 notify() 可能会导致死锁或活锁。因此,在编写多线程程序时需要谨慎设计同步策略。
  3. 异常处理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() 方法唤醒所有等待的线程,使它们重新竞争锁并进入就绪状态。

二、工作原理

  1. 线程等待:当一个或多个线程调用某个对象的 wait() 方法时,它们会释放该对象的锁并进入等待状态,直到其他线程在该对象上调用 notify()notifyAll() 方法,或者等待超时。
  2. 唤醒线程notifyAll() 方法会唤醒在该对象上等待的所有线程。被唤醒的线程会进入对象的同步队列,等待获取对象的锁之后再次进入运行状态。
  3. 锁的处理:需要注意的是,notifyAll() 方法本身并不会立即释放对象锁。锁的释放发生在调用 notifyAll() 方法的线程退出其同步块或同步方法之后。

三、使用注意事项

  1. 必须在同步块中调用wait()notify() 和 notifyAll() 方法都必须在 synchronized 方法或 synchronized 块内部调用,以确保线程在调用这些方法时持有对象的锁。
  2. 避免虚假唤醒:虽然 notifyAll() 会唤醒所有等待的线程,但有时候线程被唤醒后可能会发现条件仍未满足(即发生了“虚假唤醒”),因此通常需要在循环中检查条件。
  3. 合理设计同步策略:不当使用 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
  • 调用时机:由垃圾收集器在销毁对象之前调用,但具体何时调用是不确定的,也无法保证一定会被调用。

使用注意事项

  1. 不推荐使用:由于 finalize() 方法的不确定性和性能影响,以及从 Java 9 开始被标记为过时,因此不建议在新开发的应用中使用它。
  2. 清理资源:如果需要清理非内存资源,建议使用 try-with-resources 语句(对于实现了 AutoCloseable 或 Closeable 接口的资源)或显式地在 finally 块中关闭资源。
  3. 性能问题finalize() 方法的调用会显著影响垃圾收集的性能,因为它增加了垃圾收集器的复杂度。
  4. 安全性问题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() 是一个糟糕的做法,因为它会干扰垃圾收集器的正常工作,并可能导致性能问题。

  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值