刚学 Java 就该懂的 Object 类:所有对象的‘共同祖先’

在 Java 中,Object 类是所有类的根类。这意味着 Java 中的每个类,无论是否显式声明,都继承自 Object 类。这种隐式的继承关系是 Java 面向对象编程的核心特性之一,它为 Java 的类层次结构提供了统一的接口和行为。


1. Object 类的隐式继承

在 Java 中,每个类都隐式继承自 Object 类。即使你没有显式地声明一个类继承自 Object 类,它仍然会继承 Object 类的所有方法。这种隐式继承确保了 Java 中的所有对象都具有一些基本的行为和属性。

示例 1:隐式继承

假设我们定义了一个简单的 Car 类:

public class Car {
    private String make;
    private int year;

    public Car(String make, int year) {
        this.make = make;
        this.year = year;
    }

    public String getMake() {
        return make;
    }

    public int getYear() {
        return year;
    }
}

在这个例子中,我们没有显式地声明 Car 类继承自 Object 类。然而,Car 类仍然继承了 Object 类的所有方法,如 equalshashCodetoString 等。我们可以通过以下代码验证这一点:

public class Main {
    public static void main(String[] args) {
        Car car = new Car("Honda", 2020);
        System.out.println(car.toString()); // 调用了 Object 类的 toString 方法
        System.out.println(car.getClass()); // 调用了 Object 类的 getClass 方法
    }
}

输出结果:

Car@<hashcode>
class Car

示例 2:显式继承

虽然 Object 类的继承是隐式的,但你也可以显式地声明一个类继承自 Object 类。这在语法上是合法的,但并不常见,因为它是多余的。例如:

public class Car extends Object {
    private String make;
    private int year;

    public Car(String make, int year) {
        this.make = make;
        this.year = year;
    }

    public String getMake() {
        return make;
    }

    public int getYear() {
        return year;
    }
}

在这个例子中,Car 类显式地继承自 Object 类。然而,这并没有改变 Car 类的行为,因为 Object 类的继承已经是隐式的。

2. Object 类的关键方法

Object 类提供了许多方法,这些方法被所有类继承。以下是一些关键方法及其用途:

  • equals(Object obj):用于比较两个对象是否相等。默认情况下,它比较对象的内存地址,但通常需要重写以比较对象的状态。
  • hashCode():返回对象的哈希码值。如果重写了 equals 方法,也必须重写 hashCode 方法,以确保相等的对象具有相同的哈希码。
  • toString():返回对象的字符串表示形式。默认实现返回类名和对象的哈希码,但通常需要重写以提供更有意义的字符串表示。
  • getClass():返回对象的运行时类。这在反射编程中非常有用。
  • clone():创建对象的副本。需要实现 Cloneable 接口,并重写 clone 方法。
  • wait()notify()notifyAll():用于线程间的通信和同步。
  • finalize():在对象被垃圾回收之前调用。此方法已在 Java 9 中弃用,不推荐使用。

示例 3:重写 equalshashCode

为了正确使用 equalshashCode 方法,我们需要在 Car 类中重写它们。以下是一个示例:

public class Car {
    private String make;
    private int year;

    public Car(String make, int year) {
        this.make = make;
        this.year = year;
    }

    public String getMake() {
        return make;
    }

    public int getYear() {
        return year;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Car car = (Car) obj;
        return year == car.year && Objects.equals(make, car.make);
    }

    @Override
    public int hashCode() {
        return Objects.hash(make, year);
    }
}

示例 4:重写 toString

为了提供更有意义的字符串表示,我们可以在 Car 类中重写 toString 方法:

@Override
public String toString() {
    return "Car{" +
            "make='" + make + '\'' +
            ", year=" + year +
            '}';
}

示例 5:测试 equalshashCode

我们使用 JUnit 测试来验证 equalshashCode 方法的行为:

@Test
void givenTwoEqualCarObjects_whenComparingWithEquals_thenReturnsTrue() {
    Car car1 = new Car("Honda", 2020);
    Car car2 = new Car("Honda", 2020);
    assertTrue(car1.equals(car2));
}

@Test
void givenTwoDifferentCarObjects_whenComparingWithEquals_thenReturnsFalse() {
    Car car1 = new Car("Honda", 2020);
    Car car2 = new Car("Toyota", 2020);
    assertFalse(car1.equals(car2));
}

@Test
void givenTwoEqualCarObjects_whenComparingHashCodes_thenReturnsEqualHashCodes() {
    Car car1 = new Car("Honda", 2020);
    Car car2 = new Car("Honda", 2020);
    assertEquals(car1.hashCode(), car2.hashCode());
}

3. Object 类的多态性

Object 类的多态性是 Java 面向对象编程的核心特性之一。多态性允许我们通过一个通用的接口(如 Object 类)与各种类型的对象进行交互。这种特性在集合框架中尤为重要,因为集合框架中的类(如 ArrayListHashMap)依赖于 equalshashCode 方法的正确实现。

示例 6:多态性

假设我们有一个 Vehicle 类和一个 Car 类,Car 类继承自 Vehicle 类:

public class Vehicle {
    private String type;

    public Vehicle(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }
}

public class Car extends Vehicle {
    private String make;
    private int year;

    public Car(String type, String make, int year) {
        super(type);
        this.make = make;
        this.year = year;
    }

    public String getMake() {
        return make;
    }

    public int getYear() {
        return year;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Car car = (Car) obj;
        return year == car.year && Objects.equals(make, car.make);
    }

    @Override
    public int hashCode() {
        return Objects.hash(make, year);
    }

    @Override
    public String toString() {
        return "Car{" +
                "make='" + make + '\'' +
                ", year=" + year +
                '}';
    }
}

我们可以使用 Object 类型的变量来引用 Car 类的对象:

public class Main {
    public static void main(String[] args) {
        Vehicle vehicle = new Car("Car", "Honda", 2020);
        System.out.println(vehicle.getType()); // 输出 "Car"
        System.out.println(vehicle.toString()); // 调用了 Car 类的 toString 方法
    }
}

输出结果:

Car
Car{make='Honda', year=2020}

在这个例子中,vehicle 是一个 Vehicle 类型的变量,但它实际上引用了一个 Car 类的对象。通过多态性,我们可以调用 Car 类的 toString 方法,而不需要显式地将 vehicle 转换为 Car 类型。

4. Object 类的线程同步方法

Object 类还提供了一些用于线程同步的方法,如 wait()notify()notifyAll()。这些方法在多线程编程中非常重要,用于协调线程之间的行为。

示例 7:线程同步

假设我们有一个共享资源,多个线程需要访问和修改这个资源。我们可以使用 wait()notify() 方法来同步线程。以下是一个简单的生产者-消费者模型的示例:

import java.util.LinkedList;
import java.util.Queue;

public class ProducerConsumerExample {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        Thread producerThread = new Thread(new Producer(buffer));
        Thread consumerThread = new Thread(new Consumer(buffer));

        producerThread.start();
        consumerThread.start();
    }
}

class Buffer {
    private Queue<Integer> queue = new LinkedList<>();
    private int capacity;

    public Buffer() {
        this.capacity = 10;
    }

    public synchronized void put(int value) throws InterruptedException {
        while (queue.size() == capacity) {
            wait(); // 等待,直到有空间放置新元素
        }
        queue.add(value);
        notifyAll(); // 通知等待的消费者线程
    }

    public synchronized int get() throws InterruptedException {
        while (queue.isEmpty()) {
            wait(); // 等待,直到有元素可消费
        }
        int value = queue.poll();
        notifyAll(); // 通知等待的生产者线程
        return value;
    }
}

class Producer implements Runnable {
    private Buffer buffer;

    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                System.out.println("Produced: " + i);
                buffer.put(i);
                Thread.sleep((int) (Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Consumer implements Runnable {
    private Buffer buffer;

    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                int value = buffer.get();
                System.out.println("Consumed: " + value);
                Thread.sleep((int) (Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在这个例子中,Buffer 类是一个共享资源,生产者线程通过 put 方法向缓冲区添加元素,消费者线程通过 get 方法从缓冲区取出元素。putget 方法都是 synchronized 的,确保了对共享资源的线程安全访问。

  • wait():当缓冲区已满时,生产者线程调用 wait(),释放锁并进入等待状态,直到被唤醒。
  • notifyAll():当生产者线程向缓冲区添加元素后,调用 notifyAll() 唤醒所有等待缓冲区的线程(包括消费者线程)。
  • get():当缓冲区为空时,消费者线程调用 wait(),释放锁并进入等待状态,直到被唤醒。
  • notifyAll():当消费者线程从缓冲区取出元素后,调用 notifyAll() 唤醒所有等待缓冲区的线程(包括生产者线程)。

4.1 线程同步方法的详细解释

  • wait():使当前线程进入等待状态,并释放当前对象的锁。调用 wait() 方法的线程会暂停执行,直到其他线程调用 notify()notifyAll() 方法唤醒它。
  • notify():唤醒一个正在等待当前对象锁的线程。如果有多个线程在等待,JVM 会选择一个线程进行唤醒。
  • notifyAll():唤醒所有正在等待当前对象锁的线程。这通常用于多线程环境中的生产者-消费者模型,确保所有等待的线程都有机会被唤醒。

4.2 注意事项

  • wait()notify() notifyAll() 必须在同步代码块中调用:这些方法必须在 synchronized 块中调用,否则会抛出 IllegalMonitorStateException
  • wait() 可能会返回多次:即使没有调用 notify()notifyAll()wait() 也可能会返回。因此,建议在循环中使用 wait(),以确保条件满足后再继续执行。
  • 避免长时间的 wait():长时间的 wait() 可能会导致线程饥饿,影响程序的性能。建议在 wait() 中设置超时时间,以避免线程永久等待。

5. Object 类的 finalize() 方法

finalize() 方法在对象被垃圾回收之前调用。此方法已在 Java 9 中弃用,不推荐使用。现代 Java 实践更倾向于使用 try-with-resources 或其他显式的清理机制来管理资源。

示例 8:finalize() 方法

尽管 finalize() 方法已弃用,但了解它的行为仍然是有帮助的。以下是一个简单的示例:

public class FinalizeExample {
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Object is being garbage collected");
    }

    public static void main(String[] args) {
        FinalizeExample obj = new FinalizeExample();
        obj = null; // 使对象变为可回收状态
        System.gc(); // 建议 JVM 进行垃圾回收
    }
}

输出结果(可能不立即出现):

Object is being garbage collected

5.1 替代方案

  • try-with-resources:用于自动关闭实现了 AutoCloseable 接口的资源。
  • 显式的清理方法:在类中定义显式的清理方法,确保资源在不再需要时被正确释放。

6. 总结

Object 类是 Java 中所有类的隐式父类,它为所有对象提供了统一的接口和行为。通过继承 Object 类,Java 的类层次结构得以统一,多态性得以实现。Object 类的关键方法(如 equalshashCodetoStringwaitnotifynotifyAll)在实际开发中非常重要,正确使用这些方法可以提高代码的可读性和可维护性。如果你有任何问题或建议,欢迎在评论区留言。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐