文章 10
浏览 5554
java.lang.Object 源码解读

java.lang.Object 源码解读

java.lang.Object

一、Object类介绍

Class {@code Object} is the root of the class hierarchy.

Every class has {@code Object} as a superclass. All objects,

including arrays, implement the methods of this class.

所有类都会间接或直接继承Object类

1.1 类结构图

java.lang.Object

  • private static native void registerNatives();

  • public final native Class<?> getClass();

  • public native int hashCode();

  • public boolean equals(Object obj);

  • protected native Object clone() throws CloneNotSupportedException;

  • public String toString();

  • public final native void notify();

  • public final native void notifyAll();

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

  • public final void wait(long timeout, int nanos) throws InterruptedException;

  • public final void wait() throws InterruptedException;

  • protected void finalize() throws Throwable { }

1.2 认识native关键字


private static native void registerNatives();

static {

registerNatives();

}

native 用来修饰方法,用 native 声明的方法表示告知 JVM 调用,该方法在外部定义,我们可以用任何语言去实现它。 简单地讲,一个native Method就是一个 Java 调用非 Java 代码的接口。

  native 语法:

①、修饰方法的位置必须在返回类型之前,和其余的方法控制符前后关系不受限制。

②、不能用 abstract 修饰,也没有方法体,也没有左右大括号。

③、返回值可以是任意类型

参考文章:JAVA关键字-native

二、具体方法介绍

2.1 getClass()

返回此 Object 的运行时类。返回的 Class 对象是由所表示类的 static synchronized 方法锁定的对象。

2.2 hashCode()

返回对象的HASH(哈希值,通常通过对象的地址转换为一个int值),同一对象多次调用,每次返回的HASH值都是一样的,用于对象的比较。

2.3 equals(Object obj)

判断两个对象是否相等,其中规定相等的对象必须具有相等的哈希码。

2.4 clone() throws CloneNotSupportedException;

x.clone() != x

  • will be true, and that the expression:

x.clone().getClass() == x.getClass()

  • will be true 但这不是绝对要求。

x.clone().equals(x)

  • will be true 但这不是绝对要求

clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象。所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象。

  • 如果未实现Cloneblie接口,而使用了clone()方法,会抛出java.lang.CloneNotSupportedException: com.aoker.java.lang.clone.dto.Person

2.4.1 那么在java语言中,有几种方式可以创建对象呢?

  1. 使用new操作符创建一个对象

  2. 使用clone方法复制一个对象

那么这两种方式有什么相同和不同呢?

  • new操作符的本意是分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。

  • 而clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

2.4.2 浅克隆与深克隆

  • 浅克隆:

如果原型对象的成员变量是基本类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型(如基本类型的封装类型等),则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

  • 深克隆:

创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。总之深浅克隆都会在堆中新分配一块区域,区别在于对象属性引用的对象是否需要进行克隆(递归性的)。

2.4.3 如何使用浅克隆

  1. 首先实现Cloneable接口

public class Person implements Cloneable{

  1. 重写clone()方法,记得访问修饰符为public,这种为浅克隆,后面会讲到浅克隆和深克隆的区别

@Override

public Person clone(){

Person p = null;

try {

p = (Person) super.clone();

} catch (CloneNotSupportedException e) {

e.printStackTrace();

}

return p;

}

然后现在开始run一下


public static void main(String[] args){

Car car = new Car("五菱宏光", 50000D, 7);

Person p = new Person("五老师", 32, car);

Person cloneP = p.clone();

int pHase = p.hashCode();

int cloneHash = cloneP.hashCode();

System.out.println(1);

}

运行结果:

image.png

可见对象p和cloneP存储地址(哈希值)是不一样的,cloneP是全新的对象。

接着查看对象内部细节

image.png

对象两个对象的Car对象地址是一样, 都指向的是之前初始化的五菱神车这个对象。也就是说,对象cloneP的car属性和p的car属性指向的同一个car实例,只是复制了引用地址,并未生成新的car实例,这就是所谓的浅克隆。

2.4.4 如何使用深克隆

实现深克隆有两种方式,根据对象属性层级复杂度选择,对象层级简单的可以直接用第一种方式:

  1. 首先实现Cloneable接口,重写clone()方法,再递归实现的引用属性的克隆

  2. 采用序列化的方式

方法1:


@Override

public Person clone(){

Person p = null;

try {

p = (Person) super.clone();

if(p != null){

p.setCar(this.car.clone());

}

} catch (CloneNotSupportedException e) {

e.printStackTrace();

}

return p;

}

运行结果:

image.png

可见,car对象地址发生了改变,说明,cloneP的Car对象是重新分配了内存空间的,是一个全新的对象实例。

接下来我们改造一下Person对象

image.png

如这种Person自己有个Person属性的自包含多层级对象,如果用方法1,将会比较麻烦,转而用对象流会比较简单

方法2:

  • 首先要实现Serializable接口,让对象可进行序列化

public Person deepClone(){

ByteArrayOutputStream bo=null;

ObjectOutputStream oo=null;

ByteArrayInputStream bi=null;

ObjectInputStream oi=null;

Object object=null;

try {

bo=new ByteArrayOutputStream();

oo=new ObjectOutputStream(bo);

oo.writeObject(this);

bi=new ByteArrayInputStream(bo.toByteArray());

oi=new ObjectInputStream(bi);

object=oi.readObject();

} catch (Exception e) {

e.printStackTrace();

}finally {

if(bo!=null){

try {

bo.close();

} catch (IOException e) {

e.printStackTrace();

}

}

if(oo!=null){

try {

oo.close();

} catch (IOException e) {

e.printStackTrace();

}

}

if(bi!=null){

try {

bi.close();

} catch (IOException e) {

e.printStackTrace();

}

}

if(oi!=null){

try {

oi.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

return (Person) object;

}

@Override

public String toString() {

return "Person{" +

"name='" + name + '\'' +

", age=" + age +

", car=" + car +

", father=" + father +

'}';

}

测试代码


public static void main(String[] args){

Car cf = new Car("复兴号", 999999999D, 999);

Person pf = new Person("五老汉", 68, cf);

Car c = new Car("五菱宏光", 50000D, 7);

Person pc = new Person("五老师", 32, c, pf);

Person cloneP = pc.deepClone();

}

运行结果:

image.png

仔细观察克隆对象和被克隆对象可知,对象以及引用类型属性对象地址都不一样,且多层级属性都被克隆了,这便是深克隆的对象流的方式

2.5 toString() 方法

Object 类返回的是类名 + 符号的十六进制数字


public String toString() {

return getClass().getName() + "@" + Integer.toHexString(hashCode());

}

一般子类都建议重写这个方法。


@Override

public String toString() {

return "Person{" +

"name='" + name + '\'' +

", age=" + age +

", car=" + car +

", father=" + father +

'}';

}

2.6 notify()、notifyAll()、wait()方法

调用一个Object的wait与notify/notifyAll的时候,必须保证调用代码对该Object是同步的,也就是说必须在作用等同于synchronized(obj){......}的内部才能够去调用obj的wait与notify/notifyAll三个方法,否则就会报错:


java.lang.IllegalMonitorStateException:current thread not owner

  • wait:让当前线程进入等待状态,线程自动释放其占有的对象锁,并等待notify

  • notify:唤醒一个正在wait当前对象锁的线程,并让它拿到对象锁

  • notifyAll:唤醒在此对象锁上等待的所有线程。

notify()唤醒在此对象锁上等待的单个线程。

notifyAll()唤醒在此对象锁上等待的所有线程。

wait()让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。

wait(long timeout) 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。

wait(long timeout, int nanos) 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。

在Thread.java中,定义了join(),sleep(),yield()等方法。

join方法把指定的线程添加到当前线程中,可以不给参数直接thread.join(),也可以给一个时间参数,单位为毫秒thread.join(100)。事实上join方法是通过wait方法来实现的。比如线程A中加入了线程B.join方法,则线程A默认执行wait()方法,释放资源进入等待状态,此时线程B获得资源,执行结束后释放资源,线程A重新获取自CPU,继续执行,由此实现线程的顺序执行。

sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是它的监控状态依然保持者,当指定的时间到了又会自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。在调用sleep()方法的过程中,线程不会释放对象锁。

yield意味着放手,放弃,投降。一个调用yield()方法的线程告诉虚拟机它乐意让其他线程占用自己的位置。这表明该线程没有在做一些紧急的事情。注意,这仅是一个暗示,并不能保证不会产生任何影响。

让我们列举一下关于以上定义重要的几点:

Yield是一个静态的原生(native)方法

Yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程

Yield不能保证使得当前正在运行的线程迅速转换到可运行的状态

它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态


标题:java.lang.Object 源码解读
作者:T-Aoker
地址:https://aoaos.top/articles/2019/12/03/1575352453024.html

记录精彩的程序人生