Object类的常用方法

109 阅读4分钟

Object类的常见方法有哪些

public class Object {

   // native方法,返回当前运行时对象的class(子类对应可以直接调用该方法),使用final关键字修饰,不允许子类重写
   // 调用方式如:Class<?> clazz = new Test().getClass();
    public final native Class<?> getClass();

    // 返回对象的hashcode,主要使用在哈希表中,比如 JDK 中的HashMap。
    public native int hashCode();

    // 比较两个对象的引用地址是否相同,String类对该方法进行了重写,用于比较字符串的值是否相同
    // 一般自定义类,也会重写这个方法,比较对象的属性是否相同
    public boolean equals(Object obj) {
        return (this == obj);
    }

    // 创建并返回当前对象的拷贝,是浅拷贝
    protected native Object clone() throws CloneNotSupportedException;

    // 返回类的名字实例的哈希码的 16 进制字符串。建议Object所有的子类都重写这个方法。
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    // 唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。 简洁点的说法:唤醒一个等待锁释放的线程
    public final native void notify();

    // 唤醒在此对象监视器上等待的所有线程(唤醒等待锁释放的所有线程)
    public final native void notifyAll();

    // 等待超时之后,就暂停等待,释放锁。
    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);
    }

    // 实例被垃圾回收器回收的时候触发的操作。
    protected void finalize() throws Throwable { }
}

为什么自定义的类都要重写Object类的toString()方法

如果不重写,调用Object类的toString方法,打印出来的是对象的hash码。实际开发过程中需要记录日志来排查问题(要看下打印日志的时候是不是调用了toString方法?),一般都要看对象里面的数据到底是什么,所以一般都要重写toString方法,更加规范。

==和equals() 的区别

  • ==符号
    • 对于基本类型,==比较的是值是否相同
    • 对于引用类型,==比较的是对象的内存地址是否相同
  • equals方法
    • 不能用于比较基本类型
    • 比较引用类型时,所有的类默认都用Object类的equals方法,这个方式比较的还是对象的内存地址。所以,如果自定义的类没有重写Object的equals方法,则比较的还是内存地址;如果重写了,则按照重写的规则进行对比。一般重写的规则是比较两个对象的属性是否相同即可。

hashCode()有什么用,为什么要有hashcode

  • hashCode()方法是获取某个对象的hash值,进而用于确定某个对象在hash表中的位置
  • 比如将对象加入hashset的时候,hashset会先计算加入对象的hash值,并和已有对象的hash值比较
    • 如果没有相同hash值,则将该对象直接加入hashset
    • 否则:再调用equals()方法判断和该对象是否相同
      • 如果相同,则不加入该对象
      • 如果不同,则重新计算hash值,散列到其他位置(hash碰撞的解决方式是开放寻址法和拉链法,这个可参考文章:HashMap源码分析

hashCode()和equals()都是比较两个对象的方法,为啥JDK提供了两个呢

  • 在容器里判断是否已有某个对象的时候,用hashcode判断,效率会更高
  • 但是hashcode存在hash碰撞的情况,所以即使两个对象的hash值相同,也不一定代表两个对象就一定相同,所以需要equals()方法再做进一步的判断
  • 总结:
    • 两个对象的hascode相同,对象不一定相同
    • 两个对象的hashcode不同,则对象不相同
    • 两个对象的hashcode相同且equals比较之后也相同,则两个对象一定相同
    • 两个对象equals比较之后相同,则其hashcode也相同(这个必须要在重写equals方法的同时重写hashcode,否则也会出现equals相同,hashcode不同)

为什么重写equals方法的时候也要重写hashcode

如果只重写了equals方法,没有重写hashcode方法。可能会出现一种情况,一个类的两个对象往hashset加入的时候,用hashcode判断是不同的,所以都能插入hashset,但实际上两个类用equals方法比较,是完全相同的,在后续业务使用的时候会出现异常情况。

clone方法是深拷贝还是浅拷贝

结论:clone方法是浅拷贝(只有引用类型才会涉及到深浅拷贝)

  • 深拷贝:将对象从内存中完整的拷贝出来,并开辟一个新的内存区域存储,副本的改变不影响源对象。
  • 浅拷贝:只复制对象的引用,不复制对象本身,新旧对象共享同一块内存,副本的改变会同时修改源对象。