这些Object类的常见面试题,你了解吗?

850 阅读8分钟

Java基础:Object类面试点

1. 为什么线程通信的方法wait()、notify()、notifyAll()被定义在Object类里面?而不能定义在Thread类中?

  • 因为Java中每个对象都有一把称之为monitor(监视器)的锁,由于每个对象都可以上锁,这就要求在对象头中有一个用来保存锁信息的位置。这个锁是对象级别的,而不是线程级别的,wait()/notify()/notifyAll()也都是锁级别的操作,它们的锁属于对象,所以把它们定义在Object类中最合适。因为Object类是所有对象的父类,也就是说所有的对象都可以成为一个锁对象,所以synchronized是保证只有一个对象能在同一时刻获得锁的一种机制。
  • 而把wait()/notify()/notifyAll()方法定义在Thread类中,会带来很大的局限性,比如一个线程可能持有多把锁,以便实现相互配合的复杂逻辑,假设此时wait 方法定义在Thread 类中,如何实现让一个线程持有多把锁呢?又如何明确线程等待的是哪把锁呢?没有办法灵活的实现这样的多锁逻辑,也会增加编程难度。

2. 为什么wait(), notify()和notifyAll()必须在同步方法或者同步块中被调用?

  • (1)在同步方法或者同步块中被调用就不会抛出java.lang.IllegalMonitorStateException的异常。
  • (2)避免wait()和notify(),notifyAll()之间产生竞态条件。
  • (3)wait()是让线程等待并将锁释放出来,让给期限线程使用;notify(),notifyAll()是该线程在使用完锁后,通知其他线程可以获取锁继续执行下去。notify()是唤醒其中一个线程,notifyAll()是唤醒全部线程使其争抢。
  • (4)当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。同样的,当一个线程需要调用对象的notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。

3.wait()方法与sleep()方法的区别

44.png

  • wait()属于 Object 的成员方法,一旦一个对象调用了wait方法,必须要采用 notify() 和 notifyAll() 方法唤醒该进程;如果线程拥有某个或某些对象的同步锁,那么在调用了 wait() 后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了 wait() 方法的对象。 wait() 方法也同样会在 wait 的过程中有可能被其他对象调用 interrupt() 方法而产生 。
  • sleep()方法属于 Thread 类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了 sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。但在 sleep 的过程中过程中有可能被其他对象调用它的 interrupt() ,产生 InterruptedException 异常,如果你的程序不捕获这个异常,线程就会异常终止,进入 TERMINATED 状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有 finally 语句块)以及以后的代码。

4.hashCode()和equals()的关系

hashCode()方法的作用是获取哈希码,也称为散列码,返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。

123.image

equals()方法的作用就是判断两个对象是否相等,再Object类中是通过判断对象间的内存地址来决定是否相同。

567.image

  • hashCode()主要用于提升查询效率提高哈希表性能,来确定在散列结构中对象的存储地址,在线性表中没有作用。
  • 重写equals()必须重写hashCode();
  • 若两个对象equals()比较返回true,那么它们的hashCode值一定也相同;
  • 若两个对象equals()比较返回false,那么它们的hashCode值一定不相同;
  • 如果两个对象的HashCode值相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置。
  • 同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。

5.== 与 equals()的区别

==运算符:

  • 作用于基本数据类型时,是比较两个数值是否相等;
  • 作用于引用数据类型时,是比较两个对象的内存地址是否相同,即判断它们是否为同一个对象; equals()方法:
  • 类没有覆盖/重写 equals() 方法时,则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象的内存地址是否相同;
  • 类覆盖/重写 equals() 方法时,一般会按照对象的内容来进行比较,若两个对象内容相同则认为对象相等,否则认为对象不等。
public class test1 {
   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("true");
       }
   }
}

说明:

  • String 中的 equals ⽅法是被重写过的,因为 object 的 equals ⽅法是比较的对象的内存地址,⽽ String 的 equals ⽅法比较的是对象的值。
  • 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。

6.重写equals()方法时通常为什么也要重写一下hashCode()方法?

-String类的源码:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = count;
        if (n == anotherString.count) {
        char v1[] = value;
        char v2[] = anotherString.value;
        int i = offset;
        int j = anotherString.offset;
        while (n-- != 0) {
            if (v1[i++] != v2[j++])
            return false;
        }
        return true;
        }
    }
    return false;
    }


public int hashCode() {
    int h = hash;
    if (h == 0) {
        int off = offset;
        char val[] = value;
        int len = count;

            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
    }

Object类提供的equals()方法默认是用 == 来进行比较的,也就是说只有两个对象是同一个对象时,才能返回相等的结果。而实际的业务中,我们通常的需求是,若两个不同的对象它们的内容是相同的,就认为它们相等。鉴于这种情况,Object类中equals()方法的默认实现是没有实用价值的,所以通常都要重写

由于hashCode()与equals()具有联动关系:

  • 在Java中,Set接口代表无序的、元素不可重复的集合,HashSet则是Set接口的典型实现。
  • 当向HashSet中加入一个元素时,它需要判断集合中是否已经包含了这个元素,从而避免重复存储。由于这个判断十分的频繁,所以要讲求效率,绝不能采用遍历集合逐个元素进行比较的方式。实际上,HashSet是通过获取对象的哈希码,以及调用对象的equals()方法来解决这个判断问题的。
  • HashSet首先会调用对象的hashCode()方法获取其哈希码,并通过哈希码确定该对象在集合中存放的位置。假设这个位置之前已经存了一个对象,则HashSet会调用equals()对两个对象进行比较。若相等则说明对象重复,此时不会保存新加的对象。若不等说明对象不重复,但是它们存储的位置发生了碰撞,此时HashSet会采用链式结构在同一位置保存多个对象,即将新加对象链接到原来对象的之后。之后,再有新添加对象也映射到这个位置时,就需要与这个位置中所有的对象进行equals()比较,若均不相等则将其链到最后一个对象之后。
  • 为了保证同一个对象,保证在equals相同的情况下hashcode值必定相同,equals()方法重写时,通常也要将hashCode()进行重写,使得这两个方法始终满足相关的约定。