热爱技术,追求卓越
不断求索,精益求精

java内功心决之万法归宗,深入理解Object对象及其方法

Object

java是面向对象编程的高级程序设计语言,java内功修炼首关必过的就是Object。

java Object类信息

java Object类信息

Class Object is the root of the class hierarchy. Every class has Object as a superclass. All objects, including arrays, implement the methods of this class.

正如Object类的描述一样,java中Object是所有对象的超类,所有的类都默认继承自Object、包括数组(数组也是对象)都实现了Object类中的方法,所以这一决就是万法归宗Object。

Object之wait

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

Object的wait方法被设计为子类不可重写(Override),并且是native(和平台特性相关,java的native方法隐藏了相关的平台特性提供统一的接口给java程序使用)。调用wait的前提是当前线程持有对象的监视器(锁),当前线程获取到对象锁后,调用此方法将导致当前的线程释放当前的对象锁并且让出cpu资源,当前线程进入阻塞状态(等待),直到其他线程调用此对象的notify()方法或notifyAll()方法唤醒,或者超过指定的时间量。

如果当前线程不是锁的持有者(未被监视),调用该方法抛出一个IllegalMonitorStateException异常。

package cn.lovecto.test;

public class MyObject {

    public static void main(String[] args) throws InterruptedException {
        MyObject object = new MyObject();
        object.wait(10);
    }
}

抛出异常:

Exception in thread "main" java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at cn.lovecto.test.MyObject.main(MyObject.java:7)

所以,调用wait方法只能在同步代码块或同步方法中。

synchronized (obj) {
    while (<condition does not hold>)
        obj.wait(timeout);
    ... // Perform action appropriate to condition
}

Object中除了native的wait方法,还有另外两个wait方法,他们的内部实现都是基于native的wait方法的,只是阻塞时间上的差异。

public final void wait() throws InterruptedException {
    wait(0);
}
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);
}

需要注意的是,当设置的时间为0时,线程只会等待其他线程通过notify()方法或notifyAll()方法唤醒,否则一直阻塞下去;当设置的时间大于0,且过了等待时间,如果对象锁被其他线程持有,当前线程也会继续等待。

举个例子,美女(主线程)和美女保镖(子线程),睡衣(对象锁),美女准备睡觉,美女保镖拿出睡衣给美女,美女保镖没事就在门外候着(不情愿一直等待,只想等50毫秒),美女换上睡衣睡觉,睡觉时间内不允许别人脱美女的睡衣,美女一睡就睡10000毫秒,保镖发现50毫秒后美女还没醒,只能继续等,美女醒后,美女保镖才能拿处美女的外套换上。

package cn.lovecto.test;

public class MyObject {

    public static void main(String[] args) throws InterruptedException {
        MyObject object = new MyObject();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    try {
                        System.out.println("美女保镖给美女拿睡衣换上");
                        object.wait(50);// 只想等50毫秒
                        System.out.println("美女保镖拿出美女的外套,为美女更衣");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        // sleep 20毫秒后美女入睡
        Thread.sleep(20);
        synchronized (object) {
            System.out.println("美女睡了,别脱美女的睡衣");
            Thread.sleep(10000);// 美女要睡10000毫秒
            System.out.println("美女醒了,请为美女更衣");
        }
    }
}

执行结果:

美女保镖给美女拿睡衣换上
美女睡了,别脱美女的睡衣
美女醒了,请为美女更衣
美女保镖拿出美女的外套,为美女更衣

所以即使子线程只愿意等50毫秒,也要其他线程释放了对象的监视权,当前线程才有执行权。

所以被wait的线程,想要继续运行的话,它必须满足下面的其中一个条件:
1. 由其他线程notify或notifyAll了,并且当前线程被通知到了,经过和其他线程进行锁竞争,成功获取到锁了
2. 没有被notify或notifyAll通知到,但过了wait超时时间和其他线程进行锁竞争,成功获取到锁了

Object之notify、notifyAll

Object的notify也是不可被子类覆盖的,并且是native的。

public final native void notify();

调用notify方法将唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,并且选择是任意性的。线程通过调用其中一个wait 方法,在对象的监视器上等待。直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。所以notify会随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态,该方法只能在同步方法或同步块内部调用,如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。

notifyAll也是不可被子类覆盖的,并且是native的。

public final native void notifyAll();

notifyAll会解除所有那些在该对象上调用wait方法的线程的阻塞状态。该方法只能在同步方法或同步块内部调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。

notify和notifyAll有什么区别呢?其实在实现层面,notify和notifyAll都达到相同的效果,都只会有一个线程继续运行。但notifyAll免去了,线程运行完了通知其他线程的必要,因为已经通知过了。什么时候用notify,什么时候使用notifyAll,这就得看实际的情况了。

接下来看一个完整的wait、notify的使用实例。生产者生产产品放入仓库,仓库容量有限,消费者消费产品消耗仓库中的产品。

package cn.lovecto.test;

import java.util.LinkedList;
import java.util.List;

public class ProducerStorageConsumer {

    /** 仓库 */
    static class Storage {
        /** 仓库最大容量 */
        private int maxSize;
        /** 仓库容器 */
        private List<Object> storage;

        public Storage(int maxSize) {
            super();
            this.maxSize = maxSize;
            this.storage = new LinkedList<Object>();
        }

        /**
         * 向仓库中添加产品
         * 
         * @param object
         */
        public synchronized void add(Object object) {
            // 仓库容量满了,生产者暂停生产
            while (storage.size() == maxSize) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 生产者添加产品到仓库,通知所有消费者获取产品
            storage.add(object);
            System.out.printf("添加产品到仓库,当前库存: %d: %s\n", storage.size(), object);
            notifyAll();
        }

        /**
         * 消费者从仓库中获取产品
         * @return
         */
        public Object get() {
            Object object = null;
            synchronized (this) {
                //产品库存空了,消费者暂停消费
                while(storage.size() == 0) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                object = ((LinkedList<Object>)storage).poll();
                System.out.printf("从仓库获取产品,剩余库存: %d: %s\n", storage.size(), object);
                notifyAll();
            }
            return object;
        }
    }

    /**生产者*/
    static class Producer implements Runnable{
        /**消费者名称*/
        private String name;
        /**生产者产量*/
        private int num;
        /**仓库库存*/
        private Storage storage;

        public Producer(String name, int num, Storage storage) {
            super();
            this.name = name;
            this.num = num;
            this.storage = storage;
        }

        @Override
        public void run() {
            for(int i = 1; i <= num; i++){
                storage.add(name + "生产的第" + i + "个产品");
            }
        }
    }

    /**消费者*/
    static class Consumer implements Runnable{
        /**消费者消费量*/
        private int num;
        /**仓库库存*/
        private Storage storage;

        public Consumer(int num, Storage storage) {
            super();
            this.num = num;
            this.storage = storage;
        }

        @Override
        public void run() {
            for(int i = 0; i < num; i++){
                Object object = storage.get();
                System.out.println("取走了" + object);
            }
        }

    }

    public static void main(String[] args) throws InterruptedException {
        Storage storage = new Storage(2);
        Producer producer = new Producer("生产者" , 10, storage);
        Thread thread1 = new Thread(producer);
        Consumer consumer = new Consumer(10, storage);
        Thread thread2 = new Thread(consumer);
        thread2.start();
        thread1.start();
        Thread.sleep(1000);
    }
}

Object之hashCode

Object的hashCode方法默认实现是native的,返回的是对象的地址,hashCode是可以被重写覆盖的。

public native int hashCode();

在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运转,这样的集合包括HashMap、HashSet和HashTable等,hashCode相关内容请参考《秒懂java,覆盖equals时没有覆盖hashCode后果是多么的惨烈!》,文章详细描述了hashCode的通用约定、重写hashCode的常规步骤等内容。

Object之equals

Object的equals方法默认实现是比较对象的引用是否指向同一个对象,equals可以被子类重写覆盖。对于对象引用,==比较对象引用是否指向同一个对象。对于基本类型,比较实际内容。

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

如果类具有自己独特的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals方法以实现期望的行为,这时我们就需要覆盖equals方法。equals的相关内容请参考《秒懂java,你真的会写equals方法吗?》,文中详细描述了覆盖equals方法必须遵循的约定、正确的写equals方法的步骤、写equals方法需要注意的地方等详细内容。

Object之clone

Object的clone的作用是创建并返回此对象的一个副本,方法时native的,可以被子类重写覆盖。

protected native Object clone() throws CloneNotSupportedException; 

如果此对象的类不能实现接口Cloneable,则会抛出CloneNotSupportedException。注意:此方法执行的是该对象的“浅复制”,而不“深复制”操作。浅拷贝:对于基本数据类型,复制其数据。对于对象引用,只是复制对象引用,而不是复制对象引用所指向的对象。Object类本身不实现接口Cloneable,所以在类为Object的对象上调用clone方法将会导致在运行时抛出异常。

Object之toString

Object的toString方法返回该对象的字符串表示,Object类的toString方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at标记符“@”和此对象哈希码的无符号十六进制表示组成。

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

通常,toString方法会返回一个“以文本方式表示”的字符串,结果应是一个简明易懂的,所以建议所有子类始终重写此方法。

Object之finalize

Object的finalize方法主要用于垃圾回收,可以被子类重写覆盖。

protected void finalize() throws Throwable { }

当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。子类重写finalize方法,以配置系统资源或执行其他清除。该方法的执行通常是不可预测的,也是非常危险的,不推荐使用。

Object相关的java面试问题

  1. 对于创建一个java对象,你真的会吗,有几种方式?
  2. 你真的会写equals方法吗
  3. 覆盖equals时没有覆盖hashCode后果是多么的惨烈
  4. 两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?(Java面试经典基础问答七第9问)
  5. 多线程有几种实现方法?同步有几种实现方法?wait、sleep、notify、notityAll等方法的作用是什么?(Java面试经典基础问答五第10问)
  6. final, finally, finalize有什么区别?(Java面试经典基础问答四第10问)
  7. sleep()和 wait()有什么区别?(Java面试经典基础问答五第7问)
  8. equals方法和“==”究竟有什么区别?(Java面试经典基础问答二第1问)
  9. 写clone()方法时,通常都有一行代码,是什么?(Java面试经典基础问答三第1问)

结语

java内功修炼,Object的深入理解尤为重要,明白了Object及其相关方法的使用及实现原理,在程序设计或开发中才能做到胸有成竹游刃有余,Object是所有对象的超类,正所谓万法归宗。学习java,既要重基础,也要学框架和架构,基础是java内功,框架和架构是java外功,只有苦练内功,才有更强的外功,java学习首先得从java内功修炼开始,关于Object的内功你都Get到了嘛?

赞(1)
未经允许不得转载:LoveCTO » java内功心决之万法归宗,深入理解Object对象及其方法

热爱技术 追求卓越 精益求精