jdk源码——java.lang.Object

本文详细介绍了Java中Object类的相关方法,包括registerNatives、getClass、hashCode等。分析了各方法的源码、作用及应用场景,如hashCode用于集合元素存储定位,clone涉及浅克隆和深克隆。还提及了wait、notify等线程控制方法,以及finalize在垃圾回收时的调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


首先介绍JDK中所有类的基类——java.lang.Object
Object 类属于 java.lang 包,此包下的所有类在使用时无需手动导入,系统会在程序编译期间自动导入。Object 类是所有类的基类,当一个类没有直接继承某个类时,默认继承Object类,也就是说任何类都直接或间接继承此类,Object 类中能访问的方法在所有类中都可以调用,下面我们会分别介绍Object 类中的所有方法。

1.Object 类的结构图

Object 类由以下内容组成,之后会详细学习
Object 类由以上内容组成,之后会详细学习

2.Object 类详细介绍

2.1 registerNatives()

private static native void registerNatives();

registerNatives含义是本地注册的意思。分析:该类有static,为静态方法,同时有native修饰。native表示该方法的实现java本身并没有完成,而是有c/c++来完成,形成.dll文件。但是我们注意到,这里的{}里并没有具体的方法体,而且这里是由private修饰,那么应该如何执行呢?其实后面还一个静态代码块

static {
        registerNatives();
 }

静态代码块就是一个类在初始化过程中必定会执行的内容,所以在这里执行。
这部分貌似是执行一个C的代码,没有很理解这部分为什么会是这样,之后如果学习JVM的话,再来看这部分。

2.2 getClass()

源码这样

public final native Class<?> getClass();

Oracle的api上这样描述:getClass() 返回此运行时类Object。
那么什么是运行时的类?
个人认为,getClass()就是返回的就是Class对象,而所谓的Class对象就是.java编译生成的文件.class文件。而getClass()就是获取了这个类,这样我们可以调用这个类里面的各种各样的属性。
而Class里面有很多的方法,这些方法之后在看到java.lang.Class的时候在具体学习,大概常用的有这些吧。参考了一下api:https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html

2.3 hashCode()

源码又是一句话

public native int hashCode();

学到这里的时候我开始怀疑,这些方法作为java的基本方法,应该是由C来完成的吧。

hashCode()方法被用来获取给定对象的唯一整数。这个整数被用来确定对象被存储在HashTable类似的结构中的位置。默认的,Object类的hashCode()方法返回这个对象存储的内存地址的编号。

之前一直不理解,为什么要获取hashCode()的值,到底是有什么用处,后来在百度知道上有一位匿名的大牛写了这麽一段文字,令我茅塞顿开。

首先,想要明白hashCode的作用,你必须要先知道Java中的集合。总的来说,Java中的集合(Collection)有两类,
一类是List,再有一类是Set。你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,
但元素不可重复。那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来
判断呢?这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中
的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就
要调用1000次equals方法。这显然会大大降低效率。 于是,Java采用了哈希表的原理。哈希算法也称为散列算法,是
将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。
初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际上并不是真正的在内存的物理地址,
不过可以这样理解)。 这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到
它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;
如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。
所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。 所以,
Java对于eqauls方法和hashCode方法是这样规定的:
1、如果两个对象相同,那么它们的hashCode值一定要相同;
2、如果两个对象的hashCode相同,它们并不一定相同 上面说的对象相同指的是用eqauls方法比较。
你当然可以不按要求去做了,但你会发现,相同的对象可以出现在Set集合中。同时,增加新元素的效率会大大下降。
如果你改写了equal()方法,令两个实际不是一个对象的两个实例在逻辑上相等了,但是hashcode却是不等

2.4 equals(Object obj)

注意:这是Object里面的equals,不是String、Math等里面的equals,这个是用来比较对象是否相等的(莫非只有我不知道这俩不是一个?)
源码没啥好看的,就是这样

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

这个方法就是用来比较内存地址是否相同的一个方法,如果内存地址相同,就代表引用了同一个方法,实际上JDK中,String、Math等封装类都对equals()方法进行了重写。他的应用就比较小了,比较内存地址也很好理解,这部分就不多做记录了,我想之后我看到应该能明白。

2.5 clone()

这个东西让我看了很懵逼
源码如下

protected native Object clone() throws CloneNotSupportedException;

上面说到的问题,后来查了一下,带有native标识的都不是由java代码来实现的,请原谅我的无知。。。

这个方法看源码注释的时候让我彻底蒙了

Creates and returns a copy of this object

很好理解,但是下面举得例子我就不是很明白什么意思

x.clone() != x
will be true
x.clone().getClass() == x.getClass()
will be true
x.clone().equals(x)
will be true but this is not an absolute requirement.

主要问题还是在于x.clone().equals(x),啥时候是true,啥时候是false

主要上面理解的Object的.equals是内存地址的比较,所以我理解这个应该是false,
然后自己写了一下简单的main,代码就不贴了,结果就是x.clone().equals(x) is false。

那样就回归正题:
通过源代码可以发现几点:
1.clone方法是native方法,native方法的效率远高于非native方法,因此还是使用clone方法去做对象的拷贝而不是使用new的方法,copy。

2.此方法被protected修饰。这就意味着想要使用,必须继承它(废话,默认都是继承的)。然后重载它,如果想要使得其他类能使用这个类,需要设置成public。

3.返回值是一个Object对象,所以要强制转换才行。

好了,说到这里,国际惯例,该说说浅克隆和深克隆了。

Clone是如何完成的呢?Object在对某个对象实施Clone时对其是一无所知的,它仅仅是简单地执行域对域的copy,这就是Shallow Clone。Java Collection 类库中具体数据结构类( ArrayList/LinkedList , HashSet/TreeSet , HashMap/TreeMap 等)都具有克隆功能,且都是 Shallow Clone。在有种情况下,这种shallow Clone就会问题,这个情况就是当,要clone的对象中的某个属性是一个引用, 这样克隆类就和原始类共享了一部分信息,对这个属性操作就会影响原始类,所以就需要deep clone。

关于浅拷贝和深拷贝的具体我在网上找到一个大佬写的博客,这里直接引用过来了,写的很详细:
https://onlylove.iteye.com/blog/265113

看完之后,我还是有一点不是很理解,就是Shallow Clone,究竟可以拷贝哪些内容。什么情况下,我们需要使用Shallow Clone。希望有大佬帮忙解答一下,Shallow Clone只能copy基本数据类型的值,那么在什么样的应用场合,我们需要使用Shallow Clone呢。

2.6 toString()

啊呀呀,好兴奋啊,这个方法实在是太常用了。我发现Object我已经写了一半了,也不知道啥时候能把JDK都写完,还得上班呢,看来把源码都看了是个漫长的过程,加油。
好了,先看源码

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

这个。。。返回字符串嘛,谁都知道的啦,可是,这写的是啥啊,怎么就返回字符串了啊。
查了一些资料也没有详细的解释,后来想到,这个应该是返回此对象所在的类及对应的堆空间对象实体的首地址值。

2.7 wait(),notify(),notifyAll()

这三个方法放在一起说,都是用于控制线程状态的。这几年的开发中,不知道是不是我的水平比较low还是有意的去避开多线程,我开发的代码中尽量不会用多线程去进行操作。所以对这部分的源码没有很深的执念,暂时先摘录一下别人的理解,以后用到了再详细去分析。

public final native void notify();

public final native void notifyAll();

public final native void wait(long timeout) throws InterruptedException;

三个方法都必须在synchronized 同步关键字所限定的作用域中调用,否则会报错java.lang.IllegalMonitorStateException ,意思是因为没有同步,所以线程对对象锁的状态是不确定的,不能调用这些方法。
以下引用了某大神的一段文字:

·wait 表示持有对象锁的线程A准备释放对象锁权限,释放cpu资源并进入等待。
·notify 表示持有对象锁的线程A准备释放对象锁权限,通知jvm唤醒某个竞争该对象锁的线程X。
线程A synchronized 代码作用域结束后,线程X直接获得对象锁权限,其他竞争线程继续等待(即使线程X同步完毕,
释放对象锁,其他竞争线程仍然等待,直至有新的notify ,notifyAll被调用)。
·notifyAll 表示持有对象锁的线程A准备释放对象锁权限,通知jvm唤醒所有竞争该对象锁的线程,线程A synchronized
代码作用域结束后,jvm通过算法将对象锁权限指派给某个线程X,所有被唤醒的线程不再等待。线程X synchronized
代码作用域结束后,之前所有被唤醒的线程都有可能获得该对象锁权限,这个由JVM算法决定。

wait有三个重载方法,同时必须捕获非运行时异常InterruptedException。

wait() 进入等待,需要notify ,notifyAll才能唤醒
wait(long timeout) 进入等待,经过timeout 超时后,若未被唤醒,则自动唤醒
wait(timeout, nanos) 进入等待,经过timeout 超时后,若未被唤醒,则自动唤醒。相对wait(long timeout) 更加精确时间

上面的两段引用来源于这位大神,写的很清晰:https://blog.youkuaiyun.com/vk5176891/article/details/53945677

2.8 finalize()

哈哈Object的最后一个方法啦,撒花撒花
还是先上源码

protected void finalize() throws Throwable { }

空的,啥也没写。。。
网上查了一下,当这个对象的内存不再被使用时,在收集垃圾时就会调用这个方法。但实际上这个方法确实是只有在GC时才会被调用。
具体的应用场景,目前还没有遇到,后期看到了会更新这部分。

3 写给自己的话

希望自己能够坚持下去,每天抽出一点时间,记笔记的同时,也是 自我学习的过程,希望几年以后成为一个全面的系统架构师。

<think>好的,我现在需要帮用户查找关于JDK 8中java.lang.Package源码解析。首先,我得确认用户的需求是什么。他们可能是在学习Java的基础,或者对Package的具体实现感兴趣。用户已经提供了一些相关的引用资料,我需要仔细看看这些引用内容是否包含有用的信息。 首先,引用[1]提到java.lang包提供了基础,如Object、Math、String等,并指出Package也属于这个包。引用[2]是关于java.lang包的详解,可能包含Package的接口、结构等信息。引用[3]提到FinalReference和Cleaner是内部调用,可能和Package无关,但需要确认。引用[4]讨论集合迭代器,似乎不相关。 接下来,我需要回忆Package的作用。Package用于获取包的元数据,比如版本信息、厂商等,通常在加载时由JVM填充。用户需要源码解析,所以应该分析Package的结构、方法以及实现细节。 查看用户提供的引用,引用[1]和[2]都涉及java.lang包的结构,但可能没有详细的Package源码分析。这时候可能需要依赖已有的知识,比如PackageJDK中的实现方式,是否有静态块、私有构造方法,以及如何通过ClassLoader获取包信息等。 需要注意Package的实例由JVM创建,用户无法直接实例化。其方法如getName()、getSpecificationVersion()等如何实现?可能涉及本地方法或从特定配置文件中读取信息。 另外,用户可能想知道如何使用Package,比如获取当前的包信息,或者如何定义包信息(通过MANIFEST.MF或package-info.java)。这部分需要结合示例代码说明。 最后,根据用户要求生成相关问题,可能涉及Package的用途、与加载器的关系、如何定义包信息等。 需要确保回答结构清晰,分点解析源码结构、核心方法、包信息加载机制,并提供示例代码。同时,引用提供的参考资料,如引用[1]和[2]来支持说明Package属于java.lang包,并提供基础功能。</think>### JDK 8 java.lang.Package 源码解析 #### 1. 定义与核心作用 `java.lang.Package` 是用于描述 Java 包的元数据,提供包名、规范版本、实现版本等信息[^1]。其定义如下: ```java public class Package implements java.lang.reflect.AnnotatedElement { private final String name; private final String specTitle; private final String specVersion; // 其他字段省略... } ``` 包信息通过加载器加载,通常由 JVM 在首次加载时自动填充。 #### 2. 核心方法解析 - **静态方法 `getPackage(String name)`** 根据包名获取已加载的 `Package` 对象,依赖加载器的缓存机制。 - **`getName()`** 返回包名,如 `java.lang`。 - **`getSpecificationVersion()`** 返回规范版本(如 `JLS` 定义的版本),从 `MANIFEST.MF` 读取[^2]。 - **私有构造方法** 通过 `ClassLoader` 内部调用初始化包信息,禁止用户直接实例化。 #### 3. 包信息加载机制 包元数据通过以下方式定义: 1. **MANIFEST.MF** 文件中的属性(如 `Specification-Version`)。 2. **Annotations**(如 `@PackageSpecific`)。 3. **模块化系统(JDK 9+)**,但在 JDK 8 中仍主要依赖清单文件。 #### 4. 示例代码 ```java Package pkg = Package.getPackage("java.lang"); System.out.println("Package: " + pkg.getName()); System.out.println("Spec Version: " + pkg.getSpecificationVersion()); ``` #### 5. 关键设计特点 - **不可变对象**:所有字段为 `final`,保证线程安全。 - **懒加载**:包信息按需加载,减少内存开销。 - **与加载器绑定**:不同加载器可能返回相同包名的不同实例[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值