Java基础——Object类和Objects工具类

论坛 期权论坛 脚本     
匿名技术用户   2021-1-2 03:21   37   0

目录

1.Object类

1.1 常用方法

1.2 Object类中方法常见的问题

(1)为什么重写equals时必须重写hashCode方法?

(2)wait和notify为什么定义在Object类当中?wait和notify或notifyAll为什么必须在synchronized中去使用?

(3)wait和sleep的区别?

(4)==和equals的区别?

(5)finalize()方法什么时候被调用?

(6)finalize和C++中的析构函数有什么区别吗?

2.Objects工具类

2.1 相等判断

2.2 非空判断

2.3 计算对象的hashcode


1.Object类

1.1 常用方法

/**
 * Object类是所有类的根类,默认都会继承此类
 */
public class Object {

    private static native void registerNatives();
    static {
        registerNatives();
    }

    /**
     * native方法,用于返回当前运行时对象的Class对象,使用了 final关键字修饰,故不允许子类重写。
     */
    public final native Class<?> getClass();

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

    /**
     * 默认比较2个对象的内存地址是否相等,String类对该方法进行了重写 比较字符串的值是否相等。
     */
    public boolean equals(Object obj) {
        return (this == obj);
    }

    /**
     * naitive方法,用于创建并返回 当前对象的一份拷贝。
     * 一般情况下,对于任何对象 x,
     *      表达式 x.clone() != x 为true,
     *      x.clone().getClass() == x.getClass() 为true。
     * Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生 CloneNotSupportedException异常
     */
    protected native Object clone() throws CloneNotSupportedException;

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

    /**
     * native方法,并且不能重写。
     * 唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。
     * 如果有多个线程在等待只会任意唤醒一个
     */
    public final native void notify();

    /**
     * native方法,并且不能重写。
     * 跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程
     */
    public final native void notifyAll();

    /**
     * native方法,并且不能重写。暂停线程的执行。
     * 注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。
     */
    public final native void wait(long timeout) throws InterruptedException;

    /**
     * 多了nanos参数, 这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。
     * 所以超时的时间还需要加上nanos毫秒
     */
    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 >= 500000 || (nanos != 0 && timeout == 0)) {
            timeout++;
        }

        wait(timeout);
    }

    /**
     * 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
     */
    public final void wait() throws InterruptedException {
        wait(0);
    }

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

1.2 Object类中方法常见的问题

(1)为什么重写equals时必须重写hashCode方法?

Java关于hashCode()与equals()的规定:

  • 如果两个对象相等(即equals方法返回true),则它们的hashcode一定也是相同的,所以它们hashCode不相同的话,这两个对象肯定是不相等的
  • hashCode()是native方法,默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
  • 所以重写equals必须同时重写hashCode来保证

但是,如果两个对象的hashCode()方法的返回值相同,这两个对象也有可能不同,我们还需要用equals来判断,为什么会可能不相等呢?

  • 因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。
  • 在HashMap中的对比中,如果同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本

(2)wait和notify为什么定义在Object类当中?wait和notify或notifyAll为什么必须在synchronized中去使用?

  • 1.调用这3个方法前必须拿到当前对象的监视器monitor对象(C++中实现ObjectMonitor),也就是说notify/notifyAll和wait方法依赖于monitor对象,调用这3个方法的对象指向的monitor对象的_owner字段必须是当前线程(即我们说当前线程获取到monitor锁)
  • 2.Java中任意对象可以作为锁,就是因为每个Object对象都对应一个monitor对象,monitor对象的引用存在于对象头的Mark Word的指向重量级锁的指针中,所以这三个方法必须实现在对象当中
  • 3.而当前线程要获取对象的Monitor锁,只有synchronized关键字可以让当前线程获取monitor对象,所以这三个方法必须在synchronized代码块或者synchronized方法调用

详解见我的另两篇博文:https://blog.csdn.net/qq_34805255/article/details/101506858

https://blog.csdn.net/qq_34805255/article/details/99455696

(3)wait和sleep的区别?

  • 1.sleep是Thread类的方法,wait是Object类中定义的方法
  • 2.sleep()方法可以在任何地方使用;而wait()方法只能在synchronized方法或synchronized块中使用
  • 3.最核心的区别:Thread.sleep只会让出CPU,不会导致锁行为的改变;Object.wait不仅让出CPU,还会释放已经占有的同步资源锁
  • 4.sleep在调用的时候必须传入时间,时间到之后,线程会自动苏醒;而wait有两种方法:无限等待的wait()和有超时时间的等待wait(long),wait()被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。wait(long)超时后线程会自动苏醒。
  • 5.wait 通常被用于线程间通信,sleep 通常被用于程序暂停执行

(4)==和equals的区别?

  • == : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是 值,引用数据类型==比较的是内存地址)
  • equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
    • 情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
    • 情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等, 则返回true(即,认为这两个对象相等)。

代码示例:

public class TestEquals{
    public static void main(String[] args) {
        String a = new String("ab"); // a 为一个引用  
         String b = new String("ab"); // b为另一个引用,对象的内容一样

         String aa = "ab"; // 放在常量池中        
         String bb = "ab"; // 从常量池中查找

         if (aa == bb) // true            
            System.out.println("aa==bb");

         if (a == b) // false,非同一对象
            System.out.println("a==b");

         if (a.equals(b)) // true
            System.out.println("aEQb");
         
         //值相等
         if (42 == 42.0) // true            
            System.out.println("42=42.0");

    }
}

(5)finalize()方法什么时候被调用?

finaize方法的调用时机

  • 垃圾回收器决定回收某对象时,就会运行该对象的finalize()方法

finalize的问题:

  • 但是在Java中很不幸,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说filalize()可能永远不被执行,所以这个调用时机是不确定的,并且在不同的jvm中调用时机也是不确定的,最后,它可能还不会被执行!所以,我们并不能依赖finalize方法做一些事情。

finalize()究竟是做什么的呢?:

  • 释放非java代码创建的存储空间jiekyi
    • Java程序有垃圾回收器,所以一般情况下内存问题不用程序员操心。但有一种JNI(Java Native Interface)调用non-Java程序(C或C++),finalize()的工作就是回收这部分的内存。

(6)finalize和C++中的析构函数有什么区别吗?

finalize()方法与C++中的析构函数存在天然差别,这种差别源于语言本身机制的不同。

  • 在C++中,对象是可以在栈上分配的,也可以在堆上分配。在栈上分配的对象,也就是函数的局部变量,当超出块的"}"时,生命期便结束了。在堆上分配的对象,使用delete的时候,对象的生命期也就结束了。所以在C++中,对象的内存在哪个时刻被回收,是可以确定的
  • 在Java中,对象的分配一般在堆上,这些堆上的对象,如果无引用指向它,将等待垃圾回收器来回收其占用的内存,而垃圾回收期何时运行,无法提前预知,甚至有的时候直到程序退出都没有进行垃圾回收,程序所占内存直接由操作系统来回收。,所以在java中,对象的内存在哪个时刻回收,取决于垃圾回收器何时运行

2.Objects工具类

Objects是jdk1.7新增的,是Object的工具类,它提供了一些方法来操作对象,它由一些静态的实用方法组成,这些方法是空指针安全的或容忍空指针的,用于计算对象的hashcode,返回对象的字符串表示形式、比较两个对象。

2.1 相等判断

Objects 有提供 equals 和 deepEquals 两个方法来进行相等判断

  • equals用于判断基本类型和自定义类

  • deepEquals用于判断数组

2.2 非空判断

Objects中提供了以下非空判断

isNull和nonNull是返回boolean类型

那三个requireNonNull都是为空的话抛出异常

2.3 计算对象的hashcode

我们先看个用Idea自动生成的类的重写equals和hashCode的方法

public class Person{
 private String name;
 private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                name.equals(person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

可以看到重写的hashCode就用到了Objects.hash()方法,它的源码如下:

它接收的是可变参数,然后转变为数组交给Arrays.hashCode去执行

最终Arrays.hashCode使用已经定义好的算法,帮我们完成hashCode的计算

这样也避免了我们自己写的hashCode方法不好的问题

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP