一、Object类的结构JDK1.8源码阅读——Object类-LMLPHP

    上图为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修饰的方法有:


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;
    }
    
}
   此时编译器提示需要进行类型转换:JDK1.8源码阅读——Object类-LMLPHP

   完成类型转换后,提示需要对异常进行处理,即CloneNotSupportedException:JDK1.8源码阅读——Object类-LMLPHP

    将异常抛出后,没有发现任何编译错误,我们运行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 自动调用,一般不需要程序员去手动调用该方法。
————————————————
版权声明:本文为CSDN博主「t1heluosh1」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/PostersXu/article/details/81947715

09-30 23:31