Java源码分析之Object类(一)

一、本节目的:为了了解Object类源码实现。

二、源码展示

import jdk.internal.HotSpotIntrinsicCandidate;

/**
 * Object 类是java所有类的父类。
 */
public class Object {

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

    /**
     * JDK的源码中,被@HotSpotIntrinsicCandidate标注的方法,在	 
     * HotSpot中都有一套高效的实现,该高效实现基于CPU指令,运行时, 
     * HotSpot维护的高效实现会替代JDK的源码实现,从而获得更高的效 
     * 率。
     */
    @HotSpotIntrinsicCandidate
    public Object() {}

    /**
     * 返回当前对象的运行时Class对象。
     */
    @HotSpotIntrinsicCandidate
    public final native Class<?> getClass();

    /**
     * 返回当前对象的hash值。
     * 在一次Java应用程序执行相同的obj对象时,不管执行多少次该对象的hashCode()方法,程序总会返回一个不同的hash值。
     * 注意:
当两个对象的equals进行比较,返回true时,那么这两个对象同时调用hashCode()方法,那么返回值必然相同(请参照示例一)。
     * 反之,如果两个对象使用equals方法返回false,那么他们在调用hashCode()方法返回的值必然不同。在hash表上分配的时候,不会出现"hash碰撞"。
     * 等介绍HashMap的源码时候会重点说。。。
     */
    @HotSpotIntrinsicCandidate
    public native int hashCode();

    /**
     * Object的equals方法比较的时两个对象的内容是否相等。
     * == 比较的是两个对象的引用是否指向同一块内存。
     * 【请参照示例一】
     */
    public boolean equals(Object obj) {
        return (this == obj);
    }

    
    /**
     * 该方法返回的是一个对象的副本。而这里的"副本"的含义取决与对象是什么类型的。
     * 下面几个表达式是成立的: 
     * 1.x.clone() != x  -------- true 
     * 2.x.clone().getClass() == x.getClass()  -------true
     * 该方法可以实现对一个对象的浅拷贝。具体实现看下面示例。
     * 【请参考示例二】
     */
    @HotSpotIntrinsicCandidate
    protected native Object clone() throws CloneNotSupportedException;

    /**
     * 每一个对象的默认toString()方法,都会调用Object类的实现。
     * Class的name拼接上@符,拼上当前对象的hashCode()方法的十六进制。
     */
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

   
    /**
     * 该方法有final修饰,不能被重写。用于线程间通讯,当一个线程对象调用该方法时,会唤醒一个正在等待该对象的线程。如果有多个线程同时处于阻塞状态,那么只能唤醒一个线程。
     * 【代码案例参照示例三】
     * 
     */
    @HotSpotIntrinsicCandidate
    public final native void notify();

    
     /**
      * 可以唤醒多个正在阻塞的线程。
      * 【代码案例参照示例三】
      */
    @HotSpotIntrinsicCandidate
    public final native void notifyAll();

    /**
     * 调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)。
     * 【代码案例参照示例三】
     */
    public final void wait() throws InterruptedException {
        wait(0L);
    }

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

   
   
    public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
        if (timeoutMillis < 0) {
            throw new IllegalArgumentException("timeoutMillis value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeoutMillis++;
        }

        wait(timeoutMillis);
    }

   
    /**
     * 在JVM执行垃圾回收的时候,会执行该方法的回调。
     * 
     */
    @Deprecated(since="9")
    protected void finalize() throws Throwable { }
}

三、代码案例
示例一、

/**
* @author lengweijian
*/
public class ObjectTest {
  public static void main(String[] args) {
      m1();
      m2();
  }

  private static void m2() {
      Object o = new Object();
      Object o2 = new Object();
      Integer integer = new Integer(20);
      Integer integer2 = new Integer(20);
      System.out.printf("o为: %s\no2为: %s \no.equals(o2)结果:%s\n",o,o2,o.equals(o2));
      System.out.printf("integer为: %s\n integer2为: %s \ninteger.equals(integer2) 结果:%s\n",integer,integer2,integer.equals(integer2));
      System.out.println("----------------------------------分割线-------------------------------------");

      String str1 = "hello";
      String str2 = "hello";
      // 由于声明字符串常量直接会存储在运行时常量池中,而同一字符串常量池只存一份,所以str1和str2指向的是常量池中的同一位置。
      System.out.println(str1==str2);
      System.out.println(str1.equals(str2));
      String str3 = new String("hello");
      String str4 = new String("hello");
      // == 比较的是两个引用是否指向同一块内存区域。由于str3和str4指向的是堆内存的两块不同的区域,所以为false。
      System.out.println(str3==str4);
      System.out.println(str3.equals(str4));
  }

  /**
   * 两个Object的equals方法为true,他们的hashCode方法必然返回同一hash值
   */
  private static void m1() {
      Integer integer = new Integer(20);
      Integer integer2 = new Integer(20);
      System.out.println(integer.equals(integer2));
      System.out.println(integer.hashCode());
      System.out.println(integer2.hashCode());
  }
}


运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wytkZciV-1577337202385)(http://49.232.140.46:8090/upload/2019/10/image-42cad0ca5103433bb874ee9421ec2cef.png)]

结论: == 比较的是两个对象的引用是否指向同一块内存。equals比较的是两个对象的值是否相等。

示例二:
浅拷贝:当一个类实现了Cloneable接口,重写clone方法时。当调用该类的实例的clone方法时,会实现一个示例的拷贝。当该类的成员变量是基本数据类型的时候,拷贝的是该变量的值的副本,在修改克隆对象的基本数据类型的成员变量时,不会对源对象的基本数据类型的变量进行修改;当源类的成员变量是引用类型的时候,那么拷贝的是源类的引用类型变量的引用,当修改克隆对象的引用类型变量时,会修改源类的引用类型成员变量的值。
样例代码:
第一步:创建ObjectTest.java类

/**
 * @author lengweijian
 * Person 实现Cloneable接口,重写clone方法。
 */
class Person implements Cloneable{
    private int age;
    private Address address;
    private String name;

    public Person(int age, Address address, String name) {
        this.age = age;
        this.address = address;
        this.name = name;
    }

    .......省略setter、getter..........

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public String display() {
        return "Person [age=" + age + ", name=" + name + ", address=" + address + "]";
    }
}
public class ObjectTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        m3();
    }

    /**
     * 浅拷贝测试
     */
    private static void m3() throws CloneNotSupportedException {
        Person person = new Person(20, new Address("北京市", "回龙观东大街"), "lengwj");
        Person clonePerson = (Person) person.clone();
        System.out.println(person);
        System.out.println(clonePerson);
        System.out.println("---------------------上面输出说明了原对象与新对象是两个不同的对象。------------------------");
        System.out.println(person.display());
        System.out.println(clonePerson.display());
        System.out.println("--------------------上边可以看到拷贝出来的新对象与原对象内容一致------------------------");

        clonePerson.setName("zhanght");
        Address cloneAddress = clonePerson.getAddress();
        cloneAddress.setProvince("上海市");
        cloneAddress.setStreet("虹桥机场");
        clonePerson.setAddress(cloneAddress);

        System.out.println(person.display());
        System.out.println(clonePerson.display());
        System.out.println("我们发现,修改了克隆对象的成员引用对象的属性后,原对象的成员引用也发生了变化,这就是浅拷贝。");
    }


}


第二步,创建Address.java

public class Address {

    private String province;
    private String street;

    public Address(String province, String street) {
        this.province = province;
        this.street = street;
    }
    .......省略setter、getter、toString.....
}


运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pZhHzuL7-1577337202386)(http://49.232.140.46:8090/upload/2019/10/image-1a7157d7b355442e8397356f1ed2cdcd.png)]

示例二结论:
当Person实现了Cloneable接口时候,在Person类对象调用clone方法时候,可以完成对person对象的浅拷贝。当类的成员变量为基本数据类型的时候,拷贝的是该变量的值的副本;当变量为引用类型的时候,拷贝的是该变量的引用,而不是该对象的对象本身。所以,在修改克隆对象的基本数据类型的时候,不会对源类的基本类型数据造成改变;而在修改克隆对象的引用数据类型的时候,由于修改的是该引用所指向的对象本身,所以源类的引用类型成员变量的值所指向的对象也会发生改变。

深拷贝:了解了浅拷贝之后,深拷贝就很好理解了。就是当一个类实现了Cloneable接口,调用该类的clone方法时,会得到一个该类的拷贝。当修改该克隆对象的引用类型的成员变量时,不会修改源类的引用类型成员变量。实现深拷贝通常有以下两种方式。
第一种:
第一步,创建ObjectTest.java

package com.atlwj.jdk8.resource;
/**
 * @author lengweijian
 * 实现克隆接口
 */
class Person implements Cloneable{
    private int age;
    private Address address;
    private String name;

    public Person(int age, Address address, String name) {
        this.age = age;
        this.address = address;
        this.name = name;
    }
   ......省略gettter、setter、、、

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person clonePerson = (Person) super.clone();
        // 在这里调用引用变量的克隆方法得到引用变量的克隆,并set到克隆person对象中。
        clonePerson.setAddress((Address) clonePerson.getAddress().clone());
        return clonePerson;
    }

    public String display() {
        return "Person [age=" + age + ", name=" + name + ", address=" + address + "]";
    }
}
public class ObjectTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        m3();
    }

    /**
     * 深拷贝测试
     */
    private static void m3() throws CloneNotSupportedException {
        Person person = new Person(20, new Address("北京市", "回龙观东大街"), "lengwj");
        Person clonePerson = (Person) person.clone();
        System.out.println(person);
        System.out.println(clonePerson);
        System.out.println("---------------------上面输出说明了原对象与新对象是两个不同的对象。------------------------");
        System.out.println(person.display());
        System.out.println(clonePerson.display());
        System.out.println("--------------------上边可以看到拷贝出来的新对象与原对象内容一致------------------------");

        clonePerson.setName("zhanght");
        Address cloneAddress = clonePerson.getAddress();
        cloneAddress.setProvince("上海市");
        cloneAddress.setStreet("虹桥机场");
        clonePerson.setAddress(cloneAddress);

        System.out.println(person.display());
        System.out.println(clonePerson.display());
        System.out.println("我们发现,修改了克隆对象的成员引用对象的属性后,原对象的成员引用没有发生变化,这就是深拷贝。");
    }

}


第二步,创建Address.java类,实现Cloneable接口

package com.atlwj.jdk8.resource;

public class Address implements Cloneable{

    private String province;
    private String street;

    public Address(String province, String street) {
        this.province = province;
        this.street = street;
    }
    
.....省略getter、setter、、、、、、
    @Override
    public String toString() {
        return "Address [province=" + province + ", street=" + street + "]";
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pRhGKw9Q-1577337202386)(http://49.232.140.46:8090/upload/2019/10/image-441e7c9fc7974264bb5979e9d836f1b7.png)]

深拷贝结论:不同于浅拷贝。当调用源类的clone方法得到一个克隆对象时,对克隆对象的引用类型成员变量进行值的修改时,不会对源类的引用类型变量进行修改。做法就是在Person类的克隆方法里面,对引用变量也进行clone操作,复制到person克隆对象中进行返回,就能得到深拷贝对象。

深拷贝第二种实现:
通过实现序列化接口Serializable,可以实现对该类的全部变量包括引用类型变量完成序列化,再将该类的字节序列反序列化,就可以得到该类的深拷贝对象。
案例代码:
第一步,创建ObjectTest.java

package com.atlwj.jdk8.resource;


import java.io.*;

/**
 * @author lengweijian
 */

class Person implements Serializable {

    private static final long serialVersionUID = 7899327867106360350L;
    private int age;
    private Address address;
    private String name;

    public Person(int age, Address address, String name) {
        this.age = age;
        this.address = address;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

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

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public String getName() {
        return name;
    }

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

    public String display() {
        return "Person [age=" + age + ", name=" + name + ", address=" + address + "]";
    }
    
/**
 *
 * 通过序列化的方式完成对象深拷贝
 */
    public Object deepCopy(){
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try {
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(this);

            ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
            return objectInputStream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}
public class ObjectTest {
    public static void main(String[] args) {
        Person originPerson = new Person(30, new Address("纽约", "华尔街"), "Lucy");
        Person clonePerson = (Person) originPerson.deepCopy();
        System.out.println(originPerson);
        System.out.println(clonePerson);


        Address cloneAddress = clonePerson.getAddress();
        cloneAddress.setProvince("北京");
        cloneAddress.setStreet("中关村创业街");
        clonePerson.setAddress(cloneAddress);

        System.out.println(originPerson.display());
        System.out.println(clonePerson.display());
    }

}

第二步,创建Address.java类

package com.atlwj.jdk8.resource;

import java.io.Serializable;

/**
 * @author lengweijian
 */
public class Address implements Cloneable, Serializable {

    private static final long serialVersionUID = -8630168245854630184L;
    private String province;
    private String street;

    public Address(String province, String street) {
        this.province = province;
        this.street = street;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    @Override
    public String toString() {
        return "Address [province=" + province + ", street=" + street + "]";
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}


运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-09m03Gy0-1577337202387)(http://49.232.140.46:8090/upload/2019/10/image-25f48a1492dd410888c2a765e628fb09.png)]

示例二总结:对象的深拷贝可以通过引用的层层clone方法实现,也可以通过序列化和反序列化的方式实现。

示例三:

未完待续…

你可能感兴趣的:(Java基础部分)