Java-Object类源码解析

来源:互联网 发布:淘宝双十一晚会2017 编辑:程序博客网 时间:2024/05/14 12:52

一,Object类简述

Object类是Java中所有类的基类,在编译时会自动导入,位于java.lang包中,而Object中具有的属性和行为,是Java语言设计背后的思维体现。这里写的代码是JDK8中的,其他版本的JDK可能略有不同。
包含的方法如下图:
Object

Object类方法说明

Object类中的大部分方法都是native方法,用此关键字修饰的方法是Java中的本地方法,一般是用C/C++语言来实现。

1. 构造方法

Object类中没有显示的提供构造方法,这是编译器默认提供的。

2.registerNatives()方法

这里写图片描述
其主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。函数的执行是在静态代码块中执行的,在类首次进行加载的时候执行。

3.getClass()方法

这里写图片描述
可以看到方法用final,说明此方法不能被重写。此方法返回类运行时的类型,并且返回的类是被此类的静态同步方法锁定了(神马意思?)。

4.hashCode()方法

这里写图片描述
返回对象的哈希码,是一个整数。这个方法遵守以下三个规则:
1. 在java程序运行期间,若用于equals方法的信息或者数据没有修改,name同一个对象多次调用此方法,返回的哈希码是相同的。而在两次独立的运行java程序时,对于同一对象,不需要返回的哈希码相同
2. 如果根据equals方法,两个对象相同,则这两个对象的哈希码一定相同
3. 假如两个对象通过equals方法比较不相同,那么这两个对象调用hashCode也不是要一定不同,相同也是可以的。但是使用者应该知道对不同的对象产生不同的hashCode是可以提高hash tables的性能的。

在实际使用中,要尽量保证对于不同的对象产生不同的哈希码。hashCode的典型实现是将对象的内部地址转为一个整数,但是这种实现技术不是Java语言必须要采用的。

5.public boolean equals(Object obj)

这里写图片描述
这里写图片描述
equals方法主要是比较两个对象是否相同,Object中的equals方法比较的是对象的地址是否相同。在我们实际的编程过程中,如果要是将一个类作为hashMap等类型的键值时,则此类是需要实现equals和hashCode方法,主要是用来比较键值是否相等以及进行哈希化。

6. protected native Object clone() throws CloneNotSupportedException;

这里写图片描述
这里写图片描述
这里写图片描述
clone方法是创建并且返回一个对象的复制之后的结果。复制的含义取决于对象的类定义。这个方法一般的意图是对于对象x,能够保证以下的表达式成立。

x.clone() != xx.clone().getClass() == x.getClass();x.clone().equals(x);

这些表达式一般都为true,但是并不是绝对需要的。
按照惯例,返回的对象应该通过调用super.clone()来取得。如果一个类以及它所有的超类都服从这个惯例,那么x.clone().getClass()==x.getClass()成立。
按照惯例,clone方法返回的对象是独立于调用clone方法的对象。为了获得这种独立性,在返回克隆对象之前,需要修改对象的成员变量。即如果我们要是复制可变的对象,内部的部分成员变量是由指定可变对象的引用组成,那么我们对这些成员变量也要进行clone复制,下面有例子说明。但是如果需要复制的类的成员变量是由基本类型或者由指向不可变对象的引用组成,那么我们不需要任何修改,直接对对象调用clone方法即可。
如果一个类没有实现Cloneable接口(这个接口里面没有任何方法的声明,是一个标记接口),那么对此类的对象进行复制时,在运行时会出现CloneNotSupportedException异常。

浅拷贝例子

package com.ll.client;/*** @describe 浅复制,存在的问题* @author benjamin* @date 创建时间:2017年6月3日**/class House {    private String addr;    public House(String addr) {        this.setAddr(addr);    }    public String getAddr() {        return addr;    }    public void setAddr(String addr) {        this.addr = addr;    }}class Person implements Cloneable {    private String name;    private House house;    public Person(String name, House house) {        this.setName(name);        this.setHouse(house);    }    @Override    public Object clone() {        //浅拷贝        try {            return super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();            return null;        }       }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public House getHouse() {        return house;    }    public void setHouse(House house) {        this.house = house;    }}public class ShallowClone {    public static void main(String[] args) {        House house = new House("地址");        Person person = new Person("名字", house);        Person personClone = (Person) person.clone();        //这里我改变person对象中的house,可以看到personClone中的house也进行了变化        person.getHouse().setAddr("新地址");        System.out.println(personClone.getHouse().getAddr());     }}

深拷贝

我们将上面的浅拷贝转变为深拷贝

package com.ll.client;/*** @describe * @author benjamin* @date 创建时间:2017年6月3日**/class House implements Cloneable {    private String addr;    public House(String addr) {        this.setAddr(addr);    }    public String getAddr() {        return addr;    }    public void setAddr(String addr) {        this.addr = addr;    }    @Override    public Object clone() {        try {            return super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();            return null;        }    }}class Person implements Cloneable {    private String name;    private House house;    public Person(String name, House house) {        this.setName(name);        this.setHouse(house);    }    @Override    public Object clone() {        Person p = null;        try {            p = (Person) super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        p.house = (House) p.house.clone();        return p;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public House getHouse() {        return house;    }    public void setHouse(House house) {        this.house = house;    }}public class DeepClone {    public static void main(String[] args) throws CloneNotSupportedException {        House house = new House("地址");        Person person = new Person("名字", house);        Person personClone = (Person) person.clone();        //这里我改变person对象中的house,可以看到personClone中的house也进行了变化        person.getHouse().setAddr("新地址");        System.out.println(personClone.getHouse().getAddr());     }}

可以看到我们首先需要将House类实现Cloneable接口,其次要修改Person类中的clone方法,对于Person对象中的house成员变量也要调用clone方法,这样就完成了深拷贝。
当然实现深拷贝还有一种方式是进行对象的序列化

浅拷贝: 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝: 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

7.toString方法

这里写图片描述
可以看到Object中toString方法的实现是返回类的名称(权限定名称)加上@,然后 加上此类的哈希码的16进制表示,例如:com.ll.client.House@2a139a55

8.wait方法

在Object中存在三种wait方法,如下:

public final native void wait(long timeout) throws InterruptedException;public final void wait(long timeout, int nanos) throws InterruptedException {        if (timeout < 0) {            throw new IllegalArgumentException("timeout value is negative");        }        if (nanos < 0 || nanos > 999999) {            throw new IllegalArgumentException(                                "nanosecond timeout value out of range");        }        if (nanos > 0) {            timeout++;        }        wait(timeout);    }    public final void wait() throws InterruptedException {        wait(0);    }

可见wait()和wait(long timeout, int nanos)都在在内部调用了wait(long timeout)方法。
下面主要是说说wait(long timeout)方法
wait方法会引起当前线程阻塞,直到另外一个线程在对应的对象上调用notify或者notifyAll()方法,或者达到了方法参数中指定的时间。
调用wait方法的当前线程一定要拥有对象的监视器锁。
wait方法会把当前线程T放置在对应的object上的等到队列中,在这个对象上的所有同步请求都不会得到响应。线程调度将不会调用线程T,在以下四件事发生之前,线程T一直处于休眠状态(线程T是在其代码中调用wait方法的那个线程)

  1. 当其他的线程在对应的对象上调用notify方法,而在此对象的对应的等待队列中将会任意选择一个线程进行唤醒。
  2. 其他的线程在此对象上调用了notifyAll方法
  3. 其他的线程调用了interrupt方法来中断线程T
  4. 等待的时间已经超过了wait中指定的时间。如果参数timeout的值为0,不是指真实的等待时间是0,而是线程等待直到被另外一个线程唤醒。
    被唤醒的线程T会被从对象的等待队列中移除并且重新能够被线程调度器调度。之后,线程T会像平常一样跟其他的线程竞争获取对象上的锁;一旦线程T获得了此对象上的锁,那么在此对象上的所有同步请求都会恢复到之前的状态,也就是恢复到wait被调用的情况下。然后线程T从wait方法的调用中返回。因此,当从wait方法返回时,对象的状态以及线程T的状态跟wait方法被调用的时候一样。
    线程在没有被唤醒,中断或者时间耗尽的情况下仍然能够被唤醒,这叫做伪唤醒。虽然在实际中,这种情况很少发生,但是程序一定要测试这个能够唤醒线程的条件,并且在条件不满足时,线程继续等待。换言之,wait操作总是出现在循环中,就像下面这样:
synchronized(对象){    while(条件不满足){     对象.wait();  }  对应的逻辑处理}

如果当前的线程被其他的线程在当前线程等待之前或者正在等待时调用了interrupt()中断了,那么会抛出InterruptedExcaption异常。直到这个对象上面的锁状态恢复到上面描述的状态以前,这个异常是不会抛出的。
要注意的是,wait方法把当前线程放置到这个对象的等待队列中,解锁也仅仅是在这个对象上;当前线程在其他对象上面上的锁在当前线程等待的过程中仍然持有其他对象的锁。
这个方法应该仅仅被持有对象监视器的线程调用。
wait(long timeout, int nanos)方法的实现中只要nanos大于0,那么timeout时间就加上一毫秒,主要是更精确的控制时间,其他的跟wait(long timeout)一样,这里的代码是JDK8,在JDK6中的实现如下:

public final void wait(long timeout, int nanos) throws nterruptedException {        if (timeout < 0) {            throw new IllegalArgumentException("timeout value is negative");        }    //nanos 单位为纳秒,  1毫秒 = 1000 微秒 = 1000 000 纳秒        if (nanos < 0 || nanos > 999999) {            throw new IllegalArgumentException(                                "nanosecond timeout value out of range");        }       //  nanos 大于 500000 即半毫秒  就timout 加1毫秒       //  特殊情况下: 如果timeout为0且nanos大于0,则timout加1毫秒        if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {            timeout++;        }        wait(timeout);    }

主要点在于知道 timeout的单位为毫秒, 参数nanos 的单位为纳秒, 1毫秒 = 1000 微秒 = 1000 000 纳秒处理时,由于纳秒级时间太短(我猜测), 所以对参数nanos 其采取了近似处理,即大于半毫秒的加1毫秒,小于1毫秒则舍弃(特殊情况下,参数timeout为0时,参数nanos大于0时,也算为1毫秒)其主要作用应该在能更精确控制等待时间(尤其在高并发时,毫秒的时间节省也是很值得的)。知乎-张紫风
wait()方法就是在内部调用了wait(0)。

为什么wait方法一般要写在while循环里

一般在我们编程的时候wait方法都是写在while循环中,while循环中是测试条件,主要有以下几个原因

  • 在某个线程调用notify到等待线程被唤醒的过程中,有可能出现另一个线程得到了锁并修改了条件使得条件不在满足
  • 条件不满足,但另一个线程意外地调用了notify
  • 只有某些等待线程的条件满足了,但通知线程调用了notifyAll
  • 有可能出现“伪唤醒”

9. notify方法

通知可能等待该对象的对象锁的其他线程。由JVM(与优先级无关)随机挑选一个处于wait状态的线程。
- 在调用notify()之前,线程必须获得该对象的对象级别锁
- 执行完notify()方法后,不会马上释放锁,要直到退出synchronized代码块,当前线程才会释放锁
- notify()一次只随机通知一个线程进行唤醒

10.notifyAll()方法

和notify()差不多,只不过是使所有正在等待池中等待同一共享资源的全部线程从等待状态退出,进入可运行状态
让它们竞争对象的锁,只有获得锁的线程才能进入就绪状态
每个锁对象有两个队列:就绪队列和阻塞队列
- 就绪队列:存储将要获得锁的线程
- 阻塞队列:存储被阻塞的线程

参考链接

  1. Java并发之等待/通知机制