[TOC]

JDK8 Object

1.简述

Java对象被设计成单继承,所有的对象都直接或者间接的继承Object类,拥有Object的一些属性和方法。所以Object也可以理解为所有类的父类。

2.方法汇总

Modifier and Type Method Description
protected Object clone() Creates and returns a copy of this object.
boolean equals(Object obj) Indicates whether some other object is “equal to” this one.
protected void finalize() Called by the garbage collector on an object when garbage collection determines that there are no more references to the object.
Class<?> getClass() Returns the runtime class of this Object.
int hashCode() Returns a hash code value for the object.
String toString() Returns a string representation of the object.
void notify() Wakes up a single thread that is waiting on this object’s monitor.
void notifyAll() Wakes up all threads that are waiting on this object’s monitor.
void wait() Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
void wait(long timeout) Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.
void wait(long timeout, int nanos) Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object, or some other thread interrupts the current thread, or a certain amount of real time has elapsed.

3.方法详细信息

  1. Clone()

    //创建并返回此对象的副本。
    protected Object clone() throws CloneNotSupportedException

    如何实现拷贝,到底是浅拷贝?还是深拷贝?

    1. 要想重写clone方法,需要实现Cloneable接口
    2. 通过super.clone()获取Objec.clone()方法返回的拷贝对象
    3. 如果你想要实现深拷贝,你需要在对象返回前进行修改

    情况一:创建一个Person类,实现Cloneable接口,重写clone方法

    package com.joey.object;

    public class Person implements Cloneable {
    int age;
    String name;

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

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

    public int getAge() {
    return age;
    }

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

    public String getName() {
    return name;
    }

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

    //不重写toString()方法,为了看哈希值是否一致
    public String show() {
    return "年龄:" + this.age + ",姓名:" + this.name;
    }
    }

    class ObjectTest{

    public static void main(String[] args) throws CloneNotSupportedException {
    Person person = new Person(18,"joey");
    //深拷贝一个对象
    Person person1 = (Person)person.clone();
    //输出对象的哈希地址值
    System.out.println(person);
    System.out.println(person1);
    //输出对象的内容
    System.out.println(person.show());
    System.out.println(person1.show());
    //修改复制的对象
    person1.setAge(24);
    person1.setName("方陈勇");
    //输出判断是否会修改person的值,其实地址值不一样就已经很清楚了
    System.out.println(person.show());
    System.out.println(person1.show());
    }
    }
    /*输出结果:
    com.joey.object.Person@1b6d3586
    com.joey.object.Person@4554617c
    年龄:18,姓名:joey
    年龄:18,姓名:joey
    年龄:18,姓名:joey
    年龄:24,姓名:方陈勇
    */

    结论一:单纯看地址值能够判断两个是不同的对象,属于深拷贝,并且修改person1的值不会影响person的值。

    情况二:创建一个Address类,重写toString()方法

    package com.joey.object;

    public class Address{
    String location;

    public Address(String location) {
    this.location = location;
    }

    public String getLocation() {
    return location;
    }

    public void setLocation(String location) {
    this.location = location;
    }

    @Override
    public String toString() {
    return "Address{" +
    "location='" + location + '\'' +
    '}';
    }
    }

    Person类做一下修改

    package com.joey.object;

    public class Person implements Cloneable {
    private int age;
    private String name;
    private Address address;

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

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

    public int getAge() {
    return age;
    }

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

    public String getName() {
    return name;
    }

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

    public Address getAddress() {
    return address;
    }

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

    //不重写toString()方法,为了看地址值是否一致
    public String show() {
    return "年龄:" + this.age + ",姓名:" + this.name +",地址:" +this.address;
    }
    }

    class ObjectTest{

    public static void main(String[] args) throws CloneNotSupportedException {
    Person person = new Person(18,"joey",new Address("杭州西湖"));
    //拷贝一个对象
    Person person1 = (Person)person.clone();
    //输出对象的哈希地址值
    System.out.println(person);
    System.out.println(person1);
    //输出对象的内容
    System.out.println("**********************复制后************************");
    System.out.println(person.show());
    System.out.println(person1.show());

    //修改复制的对象
    System.out.println("*****************修改person1中的age和name*************");
    person1.setAge(24);
    person1.setName("方陈勇");
    //输出判断
    System.out.println(person.show());
    System.out.println(person1.show());

    //修改复制的对象
    System.out.println("*****************修改person1中的age和name*************");
    Address address = person1.getAddress();
    address.setLocation("杭州滨江");
    //输出判断
    System.out.println(person.show());
    System.out.println(person1.show());
    }
    }
    /*输出结果:
    com.joey.object.Person@1b6d3586
    com.joey.object.Person@4554617c
    **********************复制后************************
    年龄:18,姓名:joey,地址:Address{location='杭州西湖'}
    年龄:18,姓名:joey,地址:Address{location='杭州西湖'}
    *****************修改person1中的age和name*************
    年龄:18,姓名:joey,地址:Address{location='杭州西湖'}
    年龄:24,姓名:方陈勇,地址:Address{location='杭州西湖'}
    *****************修改person1中的age和name*************
    年龄:18,姓名:joey,地址:Address{location='杭州滨江'}
    年龄:24,姓名:方陈勇,地址:Address{location='杭州滨江'}
    */

    结论二:person1只修改age和name时发现不会影响person的值,但是修改address对象的值时就会有影响。是因为Address类没有重写Clone()方法,person和person1指向的都是同一个Address对象

    情况三:那么如何实现深拷贝。修改Address类实现Cloneable接口,重写clone()方法,在Person类中重写Clone()方法的时候调用Address重写后的clone()方法,即同时克隆Person类和Address类。

    修改Address类

      >package com.joey.object;

    >public class Address implements Cloneable{
    String location;

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

    public Address(String location) {
    this.location = location;
    }

    public String getLocation() {
    return location;
    }

    public void setLocation(String location) {
    this.location = location;
    }

    @Override
    public String toString() {
    return "Address{" +
    "location='" + location + '\'' +
    '}';
    }
    }

    修改Person类中重写的Clone()方法

       >@Override
    protected Object clone() throws CloneNotSupportedException {
    //深拷贝
    Person person = (Person)super.clone();
    person.address = (Address) this.address.clone();
    return person;
    //return super.clone();
    }

    /*输出结果:
    com.joey.object.Person@1b6d3586
    com.joey.object.Person@4554617c
    **********************复制后************************
    年龄:18,姓名:joey,地址:Address{location='杭州西湖'}
    年龄:18,姓名:joey,地址:Address{location='杭州西湖'}
    *****************修改person1中的age和name*************
    年龄:18,姓名:joey,地址:Address{location='杭州西湖'}
    年龄:24,姓名:方陈勇,地址:Address{location='杭州西湖'}
    *****************修改person1中的age和name*************
    年龄:18,姓名:joey,地址:Address{location='杭州西湖'}
    年龄:24,姓名:方陈勇,地址:Address{location='杭州滨江'}
    */

    结论三:可以看到最后person的address内容也没有改变。也就是说,当一个类里有很多引用类型时,如果想实现深拷贝那就得将每个引用类型都实现Cloneable接口重写一下clone()方法,比较麻烦的。可能好奇为什么String为什么没有影响,因为String是不可变的,相当于重新new了一个String。

    所以,对于很多引用类型,可以使用序列化对象的方式进行深拷贝。

  2. equals(Object obj)

    //直接判断的是引用,若想判断内容是否相同,需要重写此方法。
    public boolean equals(Object obj)

    常见问题:equals()和==的比较

    • equals()用于引用类型的比较,如果引用类型没有重写equals()方法,调用的就是Object类中的equals(),比较的是地址值。
    • ==用于在基本数据类型和引用数据类型的比较。如果是基本数据类型,比较的是内容,如果是引用数据类型,那比较的就是地址值。

    哪些类重写了equals()方法

    • String
    • Date
    • File
    • 包装类

    如何重写equals方法

    equals()方法里面提到了四个原则:

    1. 自反性reflexive,a.equals(a) == true
    2. 对称性symmetric,a.equals(b) == b.equals(a)
    3. 传递性transitive,当a.equals(b) == true且b.equals(c) == true时,a.equals(c) == true
    4. 一致性consistent,当equals实现不变时,a.equals(b)返回值永远不变

    例如String中的equals(),重写判断的是内容相等,hashCode()方法也重写了

    public boolean equals(Object anObject) {
    if (this == anObject) {
    return true;
    }
    if (anObject instanceof String) {
    String anotherString = (String)anObject;
    int n = value.length;
    if (n == anotherString.value.length) {
    char v1[] = value;
    char v2[] = anotherString.value;
    int i = 0;
    while (n-- != 0) {
    if (v1[i] != v2[i])
    return false;
    i++;
    }
    return true;
    }
    }
    return false;
    }

    注意:重写equals()方法,需要重写hashCode()

  3. finalize()

    //该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
    protected void finalize() throws Throwable {}

    finalize的原理

    (1)对象在初始化的过程中会判断是否重写了finalize,方法是判断两个字段标志has_finalizer_flag和RegisterFinalizersAtInit。

    (2)如果重写了finalize,那就把当前对象注册到FinalizerThread的ReferenceQueue队列中。注册之后的对象就叫做Finalizer。方法是调用register_finalizer函数。此时java虚拟机一看当前有这个对象的引用,于是就不进行垃圾回收了。

    (3)对象开始被调用,FinalizerThread线程负责从ReferenceQueue队列中获取Finalizer对象。开始执行finalize方法,在执行之前,这个对象一直在堆中。

    (4)对象执行完毕之后,将这个Finalizer对象从队列中移除,java虚拟机一看对象没有引用了,就进行垃圾回收了。

    这就是整个过程。不过在这里我们主要看的是finalize方法对垃圾回收的影响,其实就是在第三步,也就是这个对象含有finalize,进入了队列但一直没有被调用的这段时间,会一直占用内存。

    注意:应该避免使用finalize()方法,可能会导致内存溢出。

  4. getClass()

    //返回此Object运行时类类型,final修饰不可重写,一般和getName()联合使用,如getName().getClass().
    public final native Class<?> getClass();

    getClass()返回调用对象的包名+类名,jvm可以根据这个唯一定位一个类

  5. hashCode()

    //返回对象的哈希码值。
    public native int hashCode();

    这里存在一种情况,重写了equals()方法没有重写,两个对象的内容确实相等了。但是,地址值还是不一样。

    比如String类中,String a = “a”;String b = new String(“a”); a在字符串常量池中,b在堆中,地址值不一样。比如在使用集合Set或者其他一些需要唯一性判断的地方,内容相等其实就是相等,但是有两个地址值,可能会造成重复。

    所以说重写equals()方法同时也要注意重写hashCode()方法

  6. toString()

    //返回了Object对象的全限定类名+@+16进制表示的物理地址,它建议所有的子类都重写该方法,以致于可以打印出关键信息
    public String toString()
  7. notify()

  8. notifyAll()

  9. wait()

  10. wait(long timeout)

  11. wait(long timeout, int nanos)

到线程再看这几个……