揭开祖先类Object的神秘面纱❓❓

247 阅读5分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。

前言

java的世界中,万物皆对象,我们怎么理解对象呢?

相同属性的一类事物或行为我们把它封装成一个完成的域或实体,我们称这个域为对象。

java是一门面向对象编程的语言。所有的对象都只能有一个超类,使用extends关键词进行继承,只能是单继承。如果没有继承的超类,java中默认超类是Object

我们接下来详细聊聊该类下有什么方法及其作用?

protected Object clone()

创建并返回一个对象的拷贝。

clone 方法是浅拷贝,对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存

如相对应的深拷贝则会连引用的对象也重新创建,需要重写clone方法实现深拷贝逻辑。

package com.ljw.base;

/**
 * @Description: Object对象clone测试
 * <p>
 * @Author: jianweil
 * @date: 2022/1/20 18:57
 */
public class CloneTest implements Cloneable {
    // 声明变量
    String name;
    int age;

    public static void main(String[] args) {

        // 创建对象
        CloneTest cloner = new CloneTest();
        // 初始化变量
        cloner.name = "老王";
        cloner.age = 18;

        // 打印输出
        System.out.println(cloner.name); // 老王
        System.out.println(cloner.age); // 18

        try {
            // 创建 obj1 的拷贝
            CloneTest obj2 = (CloneTest) cloner.clone();
            // 使用 obj2 输出变量
            System.out.println(obj2.name); // 老王
            System.out.println(obj2.age); // 18
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("出错");
        }

    }

    /**
     * 如果对象没有实现Cloneable接口,即使复写了clone方法还是会抛出CloneNotSupportedException异常,所以很简单,实现一下Cloneable接口就可以了
     *
     * @return
     */
    @Override
    public CloneTest clone() {
        try {
            CloneTest clone = (CloneTest) super.clone();
            // TODO: copy mutable state here, so the clone can't change the internals of the original
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

注:在调用Object方法中的clone()需要子类实现Cloneable接口并重写clone方法,否则报CloneNotSupportedException错。

boolean equals(Object obj)

比较两个对象是否相等

两个值是否相等逻辑就是调用该方法。默认是判断引用地址是否相等,java中的类一般都重写了该方法实现了自己的相等逻辑。一般equals表示值相等,而==表示引用位置相等。如String类就重写了该方法

默认实现是判断地址相等:

public boolean equals (Object o) {
   return this == o;
}
package com.ljw.base;

/**
 * @Description: Equals测试
 * @Author: jianweil
 * @date: 2022/1/20 19:10
 */
public class EqualsTest {
    public static void main(String[] args) {
        
        final Object o1 = new Object();
        final Object o2 = new Object();
        System.out.println(o1.equals(o2)); // false
        System.out.println(o1==o2); // false

        // String 类使用 equals() 方法
        String obj1 = new String();
        String obj2 = new String();

        // 判断 obj1 与 obj2 是否相等
        System.out.println(obj1.equals(obj2)); // true
        System.out.println(obj1==obj2); // false

        // 给对象赋值
        obj1 = "Runoob";
        obj2 = "Google";

        // 判断 obj1 与 obj2 是否相等
        // 两个值不同,内存地址也不同,所以不相等,返回 false
        System.out.println(obj1.equals(obj2)); // false

    }
}

protected void finalize()

当 GC (垃圾回收器)确定不存在对该对象的有更多引用时,由对象的垃圾回收器调用此方法。

如果在finalize方法中复活了对象,则对象在这次垃圾回收中就可以复活一次,不被垃圾回收器回收,但是只能是一次,下次垃圾回收如果该对象不再使用了,则不会执行finalize该方法,直接回收。

所以finalize方法只能执行一次


/**
 * 测试Object类中finalize()方法,即对象的finalization机制。
 *
 */
public class CanReliveObj {
    public static CanReliveObj obj;//类变量,属于 GC Root


    //此方法只能被调用一次
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("调用当前类重写的finalize()方法");
        //复活
        obj = this;//当前待回收的对象在finalize()方法中与引用链上的一个对象obj建立了联系
    }


    public static void main(String[] args) {
        try {
            obj = new CanReliveObj();
            // 对象第一次成功拯救自己
            obj = null;
            System.gc();//调用垃圾回收器
            System.out.println("第1次 gc");
            // 因为Finalizer线程优先级很低,暂停2秒,以等待它
            Thread.sleep(2000);
            if (obj == null) {
                System.out.println("obj is dead");
            } else {
                System.out.println("obj is still alive");
            }
            System.out.println("第2次 gc");
            // 下面这段代码与上面的完全相同,但是这次自救却失败了
            obj = null;
            System.gc();
            // 因为Finalizer线程优先级很低,暂停2秒,以等待它
            Thread.sleep(2000);
            if (obj == null) {
                System.out.println("obj is dead");
            } else {
                System.out.println("obj is still alive");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
调用当前类重写的finalize()方法
第1次 gc
obj is still alive
第2次 gc
obj is dead

public final native Class<? extends Object> getClass()

获取对象的运行时对象的类

因为每个对象都有一个属于自己的类型,所以Object定义了该方法,而且是native修饰的,实现逻辑由虚拟机实现。

package com.ljw.base;

import java.util.ArrayList;

/**
 * @Description: getClass测试
 * @Author: jianweil
 * @date: 2022/1/20 19:27
 */
public class GetClassTest {
    public static void main(String[] args) {

        // getClass() with Object
        Object obj1 = new Object();
        System.out.println("obj1 的类为: " + obj1.getClass());//obj1 的类为: class java.lang.Object

        // getClass() with String
        String obj2 = new String();
        System.out.println("obj2 的类为: " + obj2.getClass());//obj2 的类为: class java.lang.String

        // getClass() with ArrayList
        ArrayList<Integer> obj3 = new ArrayList<>();
        System.out.println("obj3 的类为: " + obj3.getClass());//obj3 的类为: class java.util.ArrayList
    }
}

public int hashCode()

获取对象的 hash 值

hash值作用之一是进行快速等值判断,如果他们的hash值一样,我们再进行equals判断,Object中的equals判断是基于对象引用地址进行判断的。我们也可以自己实现我们的hashCode方法,如hashmap中的hashCode方法就很经典。

由于Object的hashCode返回值时int,理论上存在重复的现象,这时就可以判断引用地址是否一致来判断了。

/**
 * @Description: hashCode测试
 * @Author: jianweil
 * @date: 2022/1/20 19:33
 */
public class HashCodeTest {

    private int a;

    public HashCodeTest(int a) {
        this.a = a;
    }

    @Override
    public int hashCode() {
        return 1;
    }

    public static void main(String[] args) {

        // Object 使用 hashCode(),默认会更加对象地址动态计算
        Object obj1 = new Object();
        System.out.println(obj1.hashCode());//-850578890

        Object obj2 = new Object();
        System.out.println(obj2.hashCode());//-689286518

        Object obj3 = new Object();
        System.out.println(obj3.hashCode());//1792384971


        // String 重写了 hashCode()
        String str1 = new String("haha");
        String str2 = new String("haha");
        System.out.println(str1.hashCode() == str2.hashCode()); // true
        System.out.println(str1.equals(str2)); // true

        // ArrayList 使用 hashCode()
        ArrayList<Integer> list = new ArrayList<>();
        System.out.println(list.hashCode()); // 1

        final HashCodeTest hashCodeTest1 = new HashCodeTest(1);
        final HashCodeTest hashCodeTest2 = new HashCodeTest(2);
        System.out.println(hashCodeTest1.hashCode() == hashCodeTest2.hashCode()); // true
        System.out.println(hashCodeTest1.equals(hashCodeTest2)); // false
    }
}

public String toString()

返回对象的字符串表示形式

每个对象都是可打印的,所以在Object中会存在一个默认的打印方法toString。

在我们打印对象时,会调用对象的toString方法,如果我们需要根据对象的内容返回对象的字符串表示形式,则会重写该方法。

/**
 * @Description: 测试 toString
 * @Author: jianweil
 * @date: 2022/1/20 20:46
 */
public class ToStringTest {

    public static class  OverrideToString {

        String name;

        public OverrideToString(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "hello:" + this.name;
        }
    }

    public static class NoOverrideToString {
       
        public NoOverrideToString(String name) {
            this.name = name;
        }

        String name;
    }

    public static void main(String[] args) {

        final Object o = new Object();
        System.out.println(o);//java.lang.Object@bf23d05c

        final OverrideToString overrideToString = new OverrideToString("重写");
        System.out.println(overrideToString);//hello:重写

        final NoOverrideToString noOverrideToString = new NoOverrideToString("没有重写");
        System.out.println(noOverrideToString);//com.ljw.base.ToStringTest$NoOverrideToString@973ff776

    }
}

notify

由于java支持多线程进行同步操作,都是可以协调操作,并且搭配synchronize一起使用,synchronize可以锁住一个对象的,并在这个对象的对象头加锁,只要获取到锁才能执行相关临界资源。

锁也是每个对象都可以使用的,所以Object类把notify通知相关的方法和wait等待相关的方法放到Object类,这些方法共同协助运行,使程序更加高效运行。

public final native void notify()

唤醒在该对象上等待的某个线程

public final native void notifyAll()

唤醒在该对象上等待的所有线程

wait

public final void wait()

让当前线程进入等待状态。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。

public final void wait(long time)

让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过参数设置的timeout超时时间。

public final native void wait(long timeout, int frac)

与 wait(long timeout) 方法类似,多了一个 frac 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。

/**
 * @Description: 测试 wait和 notify
 * @Author: jianweil
 * @date: 2022/1/20 20:29
 */
public class WaitNotifyTest {

    private static Object locker = new Object();

    public static void main(String[] args) throws InterruptedException {
        WaitNotifyTest test = new WaitNotifyTest();

        // 启动新线程,防止主线程被休眠
        new Thread(() -> {
            try {
                synchronized (locker) {
                    System.out.println("wait start");
                    //执行 wait()
                    locker.wait();
                    System.out.println("wait end");
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // 为了确保 wait() 先执行再执行 notify()
        Thread.sleep(200);


        synchronized (locker) {
            System.out.println("notify start");
            //执行 notify ()
            locker.notify();
            System.out.println("notify end");
        }
    }
}
wait start
notify start
notify end
wait end

可以重写Object方法有哪些

根据方法作用域可以判断:

  • protected Object clone()
  • boolean equals(Object obj)
  • protected void finalize()
  • public int hashCode()
  • public String toString()

拾遗-wait和sleep区别

  • wait和synchronize一起使用,而sleep不需要
  • 进入 wait 状态的线程能够被 notify 和 notifyAll 线程唤醒,而 sleep 状态的线程不能被 notify 方法唤醒
  • wait 通常有条件地执行,线程会一直处于 wait 状态,直到某个条件变为真,但是 sleep 仅仅让你的线程进入睡眠状态
  • wait 方法会释放对象锁,但 sleep 方法不会释放对象锁。

写在最后

  • 👍🏻:有收获的,点赞鼓励!
  • ❤️:收藏文章,方便回看!
  • 💬:评论交流,互相进步!