JDK1.8源码阅读——Object类

    之前虽有在一些公众号和博客上看过jdk的源码,但是阅读他人的所得难免容易忘记,故决定抽出时间去阅读、总结jdk的源码知识,以此加深印象,巩固SE的基础。

    首先推荐一篇博文,关于jdk源码阅读的顺序:JDK源码阅读顺序

    OK,下面开始正题。

一、Object类的结构

    上图为Object类的结构树,由此可以清晰的看到整个Object的架构。其中个人经过搜索、日常开发的总结,认为Object、clone、equals(Object)、hashCode、getClass、toString这几个方法相对重要(仅属个人意见,如有不同之见,欢迎讨论)。可能有人认为notify、wait等线程有关的方法也很重要,但是从个人角度出发,我认为这些方法更应该放在线程里去研究和讨论。

二、native方法介绍

    我们都知道,Java的底层是通过C、C++等语言实现的,那么Java是如何区分这些方法并能准确地去调用的呢?

2.1 native关键字

    在Java中,如果一个方法使用native关键字来修饰,即表明该方法并不是由Java实现的,它是由non-java即C、C++负责实现。其具体的实现方法被编译在了dll文件中,由Java调用。由native修饰的方法有:

private static native void registerNatives();

public final native Class<?> getClass();

public native int hashCode();

protected native Object clone() throws CloneNotSupportedException;

public final native void notify();

public final native void notifyAll();

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

2.2  registerNatives()

    registerNatives(),顾名思义,注册native修饰的方法,其作用是将C、C++等方法映射到Java中由native修饰的方法,这也是Java为何能做到准确地去调用non-java方法。其源码如下:

private static native void registerNatives();

    可以看到,这里并没有执行此方法。Java使用的是静态代码块去执行registerNatives(),源码如下:

static {
    registerNatives();
}

2.3   clone()

protected native Object clone() throws CloneNotSupportedException;

    通过上述源码,我们知道,clone不是Java原生的方法,且Object提供的复制是浅复制,不是深度复制。

    浅复制是指只复制对象的引用,而深度复制则是将原来复制的对象完完全全的复制出来,此时被复制的对象与复制出来的对象已经没有任何关系,概括起来就是,浅复制复制引用与值,引用与值都不变;深度复制复制值,引用变值不变。

    可以看到,clone方法显式抛出不支持复制的异常,这说明,实现对象的复制是有条件的。

    1)  方法由protected修饰,说明若要实现复制,需要继承Object(默认都是继承Object的...)

    2)返回类型为Object,表明若要得到我们想要复制的结果需要进行类型转换

    3)实现Cloneable接口,否则会抛出不支持复制的异常

    以下为一段测试clone的代码:

class Son { 
	
	private Integer sonAge;
	
	private String sonName;

	public Son(Integer sonAge, String sonName) {
		super();
		this.sonAge = sonAge;
		this.sonName = sonName;
	}

	public Son() {
		super();
	}

	public Integer getSonAge() {
		return sonAge;
	}

	public void setSonAge(Integer sonAge) {
		this.sonAge = sonAge;
	}

	public String getSonName() {
		return sonName;
	}

	public void setSonName(String sonName) {
		this.sonName = sonName;
	}
}
public class Parent {

	public static void main(String[] args) throws CloneNotSupportedException {
		
		Son son = new Son(10,"清然");
		
		Parent parent = new Parent(20,"安然",son);
		Parent temp1 = parent.clone();
	}
	
	private Integer age;
	
	private String name;
	
	private Son son;
	
	public Parent(Integer age, String name, Son son) {
		super();
		this.age = age;
		this.name = name;
		this.son = son;
	}

	public Parent() {
		super();
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Son getSon() {
		return son;
	}

	public void setSon(Son son) {
		this.son = son;
	}
	
}

   此时编译器提示需要进行类型转换:

   完成类型转换后,提示需要对异常进行处理,即CloneNotSupportedException:

    将异常抛出后,没有发现任何编译错误,我们运行main方法,控制台出现如下错误:

Exception in thread "main" java.lang.CloneNotSupportedException: jdkreader.java.lang.object.Parent
	at java.lang.Object.clone(Native Method)
	at jdkreader.java.lang.object.Parent.main(Parent.java:16)

   异常显示Parent并不支持clone,此时我们需要实现Cloneable接口:

public class Parent implements Cloneable

   我们可以看一下复制后的两个对象的关系:

Son son = new Son(10,"清然");
		
Parent parent = new Parent(20,"安然",son);
Parent temp1 = (Parent) parent.clone();
Parent temp2 = (Parent) parent.clone();
		
System.out.println("temp1 == temp2 : " + (temp1 == temp2));

    运行结果为:

temp1 == temp2 : false

    很明显,结果为false,因为复制出来是一个新的对象,引用不同。我们再对他们的各个变量进行一一比较:

System.out.println("temp1Age == temp2Age : " + (temp1.getAge() == temp2.getAge()));
System.out.println("temp1Name == temp2Name : " + (temp1.getName() == temp2.getName()));
System.out.println("temp1Son == temp2Son : " + (temp1.getSon() == temp2.getSon()));

    运行结果为:

temp1Age == temp2Age : true
temp1Name == temp2Name : true
temp1Son == temp2Son : true

   很奇怪,他们的内部属性却全部是相同的,这是为什么呢?

   因为Object提供的clone是浅复制,如果是基本类型,则复制其值,如果是引用内容,则复制其引用。所以两者指向的地址是相同的,故相等。

    2.4  getClass

public final native Class<?> getClass();

    此方法返回的是运行时类对象,这点从注释可明显看出:

Returns the runtime class of this {@code Object}.

    什么是类对象?我们知道,在Java中,一切皆对象。在Java中,类是是对具有一组相同特征或行为的实例的抽象并进行描述,对象则是此类所描述的特征或行为的具体实例。作为概念层次的类,其本身也具有某些共同的特性,如都具有类名称、由类加载器去加载,都具有包,具有父类,属性和方法等。于是,Java中有专门定义了一个类,Class,去描述其他类所具有的这些特性。

  2.5  hashCode

public native int hashCode();

    这也是由non-java实现的方法,返回的是一个对象的散列值,类型为int。一般情况下,在当前程序的运行期间,一个对象多次调用hashCode方法,其返回的散列值是相同的。这里需要注意的是:

    若两个对象相等,则它们的散列值一定相等,反之,散列值相等,两个对象不一定相等;

    若两个对象不相等,则它们的散列值不一定相等,反之,散列值不同,两个对象一定不相等。

    在Java中,有许多地方都应用到了hash,如集合set、map,equals等,这里不做过多研究。

2.6  equals

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

    关于“==”与equals的区别,面试的时候多数人都被问到过。我们知道“==”比较基本数据类型时,比较的是值,当比较对象时,比较的是其引用;而equals比较的是两个对象是否相等。

    在Object类中,equals与“==”其实是等价的,但是我们在很多情况下是需要重写equals方法的,例如比较两个学生是否为同一个人,我们直接使用equals方法肯定是不可行的。通常情况下,我们认为如果学号相同,那么这两个学生就是同一个人。重写的示例如下:

    Student类

/**
 * 用于测试重写Object类equals方法
 * 
 * @author xuyong
 *
 */
public class Student {

	private String no;		//学号
	
	private String name;	//姓名

	public Student(String no, String name) {
		super();
		this.no = no;
		this.name = name;
	}

	public Student() {
		super();
	}

	public String getNo() {
		return no;
	}

	public void setNo(String no) {
		this.no = no;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

    测试类

Student stu1 = new Student("1", "路人甲");
Student stu2 = new Student("1", "路人乙");
System.out.println(stu1.equals(stu2));

    运行,控制台打印false。此时,我们重写一下Student类的equals方法:

    @Override
	public boolean equals(Object obj) {
		if (obj instanceof Student) {
			Student stu = (Student)obj;
			return no.equals(stu.getNo());
		}
		return super.equals(obj);	
	}

    再次运行刚刚的代码,控制台打印true。于是,我们便实现了通过学号判断是否为同一个人的业务。

    不过,骚年们,以为这就结束了吗?NO!

    由于Java需要维护hash的一般规律,没错就是刚刚标红的内容:两个对象相等,那么他们的散列值必须相等。

    但是此时,我们测试一下他们的hash是否相等,可以明显发现,他们是不相等的。

System.out.println(stu1.hashCode() == stu2.hashCode());

    所以,我们在重写equals方法时,必须要重写hashCode方法,由于我们是通过学号判断的,所以最好也是使用学号的散列值替代原有的散列值:

    @Override
	public int hashCode() {
		return no.hashCode();
	}

    此时再运行方法,就会发现返回的是true了。

2.7  finalize

protected void finalize() throws Throwable { }

该方法用于垃圾回收,一般由 JVM 自动调用,一般不需要程序员去手动调用该方法。

 

OK,全文结束啦~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值