JAVA:Object源码学习

 记录Object源码学习,牵扯到native方法具体实现的,暂时不去深究,简单说说方法意义。

上图来自Object源码解析

        Object类,所有类的根类,位于java.lang。

        在Java中,native关键字用于标识一个方法是由非Java语言实现的,也就是说这个方法的实现是由其他语言(如C或C++)编写的,Object中的大部分方法都是native方法。

        这些native方法的具体实现都是由JVM实现的,因为可能这些功能无法通过Java语言本身来实现,例如访问底层操作系统或硬件资源。

        在Java的设计中,先有的是JVM的功能,然后根据JVM的功能设计了Object类中的native方法的声明。

        Java的设计者首先定义了Java语言的规范和虚拟机的功能,其中包括了一些与底层操作系统和硬件交互的功能。为了实现这些功能,Java语言引入了native关键字,用于声明本地方法,即由其他语言实现的方法。

        然后,根据JVM的功能需求,Java的设计者在Object类中声明了一些native方法,这些方法提供了与底层操作系统和硬件交互的能力。这些native方法的具体实现是由JVM提供的,JVM会根据操作系统和硬件平台的不同,在JVM的本地代码库中实现这些方法。

1.registerNatives()方法

    private static native void registerNatives();
    static {
        registerNatives();
    }

        static{}是一个静态代码块,它在类加载的时候执行,且只会执行一次。静态代码块用于在类加载时进行一些初始化操作,比如初始化静态变量或调用静态方法。

        这个函数的作用是将Object的native方法注册到JVM中,通过注册本地方法,Object类中的native方法与JVM中的本地代码库建立了联系,使得Java程序能够通过调用Java类的方法来间接调用底层的本地方法。

2.getClass()方法

public final native Class<?> getClass();

        这个函数返回一个对象的运行时类,即对象所属的类的Class对象。它是Object类中的一个成员方法,因此所有的Java对象都可以调用该方法。

        通过调用getClass()方法,可以获取一个对象的实际类型信息,包括类的名称、包的名称、父类、接口等。该方法返回的是一个Class对象,可以通过该对象进行一些反射操作,比如获取类的构造函数、方法、字段等信息。

示例代码:

public class Main {
    public static void main(String[] args) {
        String str = "Hello World";
        Class<?> clazz = str.getClass();   
        /*<?>通配符,表示一个具体的未知类,Class 是一个特殊的类,用于表示类的元数据信息*/
        System.out.println(clazz.getName()); // 输出:java.lang.String
        System.out.println(clazz.getSimpleName()); // 输出:String
    }
}

在上面的示例中,通过调用str对象的getClass()方法,获取了String类的Class对象。然后可以通过Class对象的getName()方法获取类的全限定名,通过getSimpleName()方法获取类的简单名称。

3.hashCode()方法

public native int hashCode();

hashCode() 方法是 Object 类中的一个成员方法,它返回对象的哈希码(hash code)。

哈希码是一个整数值,用于快速确定对象在哈希表中的位置。哈希表是一种常用的数据结构,用于实现集合类(如 HashSet)和映射类(如 HashMap)。在哈希表中,对象的哈希码用于确定对象在内部数组中的索引位置,从而可以快速地进行查找、插入和删除操作。

哈希码的计算通常是基于对象的内容,因此对于相同内容的对象,它们的哈希码应该是相同的。但是,由于哈希码是一个整数值,它的范围有限,因此不同的对象可能会产生相同的哈希码,这种情况称为哈希冲突。

在 Java 中,hashCode() 方法的默认实现是根据对象的内存地址计算哈希码。但是,根据需要,我们可以重写 hashCode() 方法,根据对象的内容计算哈希码,以提高哈希表的性能和减少哈希冲突的概率。

重写 hashCode() 方法时,需要遵循以下规则:

  • 如果两个对象通过 equals() 方法相等,那么它们的哈希码必须相等。
  • 如果两个对象的哈希码相等,它们不一定相等。

示例代码:

public class Person {
    private String name;
    private int age;

    // 构造函数、getter和setter方法省略

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + name.hashCode();
        result = 31 * result + age;
        return result;
    }
}

在上面的示例中,重写了 hashCode() 方法,根据对象的 name 和 age 属性计算哈希码。通过这种方式,相同内容的对象将具有相同的哈希码,从而可以更好地利用哈希表的性能优势。

所以说,在后面的类方法实现中,很多时候视情况而定,重写hashCode()方法适应具体情况。

4.equals(Object obj)方法

    public boolean equals(Object obj) {
        return (this == obj);
    }

 非native方法。

这里很明显能看出是比较两个对象是否相同,但是Object这里比较的是地址,后面很多时候不一定是地址,也有可能是键值等数据比较,再根据情况进行重写即可。

5.clone()方法

protected native Object clone() throws CloneNotSupportedException;

该方法用于创建并返回当前对象的一个副本(即克隆对象)。

克隆是指创建一个与原始对象具有相同状态的新对象。通过克隆,可以在不使用构造函数的情况下创建一个对象的副本,从而避免了重新分配内存和初始化对象的开销。

在 Java 中,通过实现 Cloneable 接口,可以标记一个类为可克隆的,没有定义任何方法。如果一个类没有实现 Cloneable 接口但调用了 clone() 方法,会抛出CloneNotSupportedException异常。

需要注意的是,clone() 方法创建的是浅拷贝(shallow copy)。如果需要创建深拷贝(deep copy),即复制对象及其内容,需要自行实现深拷贝的逻辑。

深浅拷贝讲解

示例代码:

public class Person implements Cloneable {
    private String name;
    private int age;

    // 构造函数、getter和setter方法省略

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();   //调用父类Object的clone方法
    }
}

在上面的示例中,Person 类实现了 Cloneable 接口,并重写了 clone() 方法。通过调用 super.clone(),可以创建并返回当前对象的一个副本。需要注意的是,clone() 方法的返回类型是 Object,因此需要进行类型转换。

使用 clone() 方法时,可以通过以下方式进行克隆:

Person person1 = new Person("Alice", 20);
Person person2 = (Person) person1.clone();

通过 clone() 方法创建的克隆对象和原始对象是独立的,修改其中一个对象的属性不会影响另一个对象。

6.toString()方法

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

非native方法。

toString()方法返回的字符串表示了该对象的类名和哈希码。Integer.toHexString(hashCode())接受的是一个十进制数,这里也就是哈希码,这个函数功能是将十进制转为十六进制字符串表示。

7.notify()方法 

public final native void notify();

用于唤醒在该对象上等待的单个线程。如果有多个线程等待该对象,则只会唤醒其中一个线程,并且是由线程调度器决定唤醒哪个线程。

该方法的作用和意义在于实现线程间的通信。当一个线程在某个对象上调用了该对象的wait()方法后,它会进入该对象的等待集,并且释放对象上的锁。此时,其他线程可以调用该对象的notify()方法来唤醒等待集中的一个线程,使其重新竞争对象上的锁。

通过使用wait()和notify()方法,可以实现线程间的协作和同步。例如,一个线程在生产者-消费者模型中生产了一个数据项后,可以调用该数据项对象的notify()方法,通知消费者线程可以开始消费了。

需要注意的是,notify()方法只能在同步代码块或同步方法中调用,因为它需要获取对象上的锁来唤醒等待的线程。如果在非同步的上下文中调用notify()方法,会抛出IllegalMonitorStateException异常。

 下面就是一个生产者消费者模型的简单实现通信。


public class HelloWorld {
    public static void main(String[] args) {
        final Data data = new Data();

        // 创建生产者线程
        Thread producerThread = new Thread(() -> {
            synchronized (data) {    //同步块内
                // 生产数据
                data.setData("Hello World");

                // 通知消费者线程可以开始消费
                data.notify();
            }
        });

        // 创建消费者线程
        Thread consumerThread = new Thread(() -> {
            synchronized (data) {
                try {
                    // 等待生产者线程生产数据
                    data.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                // 消费数据
                System.out.println("Consumed data: " + data.getData());
            }
        });

        // 启动线程
        producerThread.start();
        consumerThread.start();
    }

    static class Data {
        private String data;

        public String getData() {
            return data;
        }

        public void setData(String data) {
            this.data = data;
        }
    }
}

在这段代码中,生产者线程在生产数据后确实会通过data.notify()方法通知消费者线程可以开始消费。然而,由于线程的调度机制是由操作系统控制的,我们无法保证生产者线程在发送通知后立即让出CPU执行时间片,而消费者线程立即获得CPU执行时间片并开始执行。

因此,在某些情况下,生产者线程可能会在发送通知后继续执行一段时间,而消费者线程可能需要等待一段时间才能获得CPU执行时间片并开始执行。这就导致了只有一个"生产数据"的输出,而"Consumed data: Hello World"的输出可能会稍后才出现。

这种情况下,我们无法准确预测生产者和消费者线程的执行顺序。在某些情况下,消费者线程可能会在生产者线程发送通知之前开始执行,这样就会导致输出顺序为"Consumed data: Hello World"先于"生产数据"。

8.notifyAll()方法

public final native void notifyAll();

很明显,它用于唤醒正在等待该对象锁的所有线程。

当一个线程调用了某个对象的notifyAll()方法后,该对象上等待的所有线程都会被唤醒,然后它们会开始竞争这个对象的锁。

被唤醒的线程会从等待状态转变为可运行状态,但并不表示它们会立即获得锁。只有当持有该对象锁的线程释放锁后,唤醒的线程才能有机会获取到锁并执行。

notifyAll()方法和notify()方法一样,只能在同步代码块或同步方法中调用,否则会抛出IllegalMonitorStateException异常。

9.wait方法

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);
}


public final void wait() throws InterruptedException {
        wait(0);
}

这里实现了三个wait函数的重载,第三个wait()很简单,就是调用第一个wait函数,传参为0。

第一个wait(),用于使当前线程进入等待状态,直到其他线程调用该对象的notify()方法或notifyAll()方法,或者指定的等待时间到期。

当一个线程调用了某个对象的wait(long timeout)方法后,它会释放该对象的锁,并进入等待状态。在等待状态下,该线程不会参与到锁的竞争,也不会执行任何代码,直到以下三种情况之一发生:

  1. 其他线程调用了该对象的notify()方法或notifyAll()方法,唤醒了等待的线程。
  2. 指定的等待时间到期,即等待超时。
  3. 当前线程被中断。

如果等待时间大于0,则表示在指定的等待时间内等待,如果等待时间小于等于0,则表示一直等待下去,直到被唤醒。所以可以想到,第三个wait()函数的目的就是使进程一直等待下去。

wait(long timeout)方法只能在同步代码块或同步方法中调用,否则会抛出IllegalMonitorStateException异常。另外,该方法会抛出InterruptedException异常,当线程在等待状态中被中断时会抛出该异常。

wait()方法的应用举例已经在notify中说明,不再过多解释。

另外说明,timeout代表的等待时间是毫秒级的。

第二个wait的nanos代表的是额外的纳秒时间,看代码很容易理解,先对timeout和nanos进行数据的合理化判断。这里不同于第一个wait()的是,当timeout小于0是直接抛出异常而不是一直等待。

主要讲一下为什么要对timeout加1:
在Java的线程调度中,时间片是以毫秒为单位进行分配的。线程调度器会按照一定的策略在不同的线程之间切换执行。如果一个线程在等待的过程中发生了上下文切换,即切换到其他线程执行,然后又切换回来,那么实际等待的时间就会少于timeout毫秒。

为了避免这种情况,可以将timeout参数加1,以确保至少等待timeout + 1毫秒的时间。这样即使发生了上下文切换,也能够保证等待的时间不会少于timeout毫秒。

10.finalize()方法

protected void finalize() throws Throwable { }

这里仅仅只是一个空函数,可供子类针对自己的情况进行重写。它是Java中的垃圾回收机制的一部分。当一个对象不再被引用时,垃圾回收器会在适当的时候调用finalize()方法来释放对象占用的资源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值