Java 中 equals 与 == 的区别
通俗的来说,“==” 比较的是地址,equals 比较的是内容。也就是说前者比较的是对象(堆)在(栈)内存中存放的内存地址,用来判断两个对象的地址是否相同,即是否是指向同一个对象。后者用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object 类的,所以适用于所有对象,注意如果没有对该方法进行重写的话,调用的仍然是 Object 类中的方法,而 Object 中的 equals 方法返回的却是内存地址比较的结果,方法内容如下 :
1 | public boolean equals(Object obj) { |
hashCode 和 equals 方法的区别与联系
hashCode 和 equals 方法都是 Object 中的方法,其中 hashCode 方法是 Native 修饰,该方法是计算出对象实例的哈希码,又称哈希函数,计算方式依赖于对象实例的内存地址,所以一般来说,每个对象实例的哈希码都是唯一的,当然如果对该方法进行重写了,结果也就不一样了。而 equals 方法一般是用来比较两个对象实例的值是否相等,当然如果没有对该方法进行重写,比较的就是两个对象的地址是否相等。
他们之间的联系就是当两个对象的 equals 相等那么 hashCode 一定相等,hashCode 不等那么equals 一定不等。反之 hashCode 相等,equals 不一定相等,因为哈希散列值有冲突的时候,当然好的哈希算法冲突的几率比较小。
其次在我们开发当中,一般都会同时对这两个方法进行重写,如果只重写其中一个或者都不重写当我们将这个对象放入 Map 集合或者 Set 集合中时就会出问题了。如果只重写了 hashCode 方法没有重写 equals 方法,那么就会出现 hashCode 值相同时,这时找到数组同一个位置的元素链表,由于没有重写 equals 方法导致向 Map 中取元素时找不到你要找的元素;当向 Map 集合中放入元素时就会放入重复的元素,因为此时比较的是两个元素的内存地址。如果只重写了 equals 方法没有重写 hashCode 方法,当你向 Map 中获取元素时,第一步比较 hashCode 值时就已经不等,所以也就找不到你想要找的元素了;当你向 Map 中放入元素时,第一步比较就始终定位在数组的不同的位置,这样也就达不到覆盖 key 值相同的元素,Set 集合也达不到去重的效果了。
还有就是对于需要大量并且快速的对比的话如果都用 equals 方法做比较显然效率太低,所以解决方式是每当需要对比的时候,首先用 hashCode 方法进行对比,如果hashCode 不一样,则表示这两个对象肯定不相等(也就是不必再用 equals 对比了),如果 hashCode 相同,此时再通过 equals 方法对比,如果 equals 也相同则表示这两个对象是真的相同了,这样既能大大提高效率也保证对比的绝对正确性!
总之,这两个方法对实现 HashMap 的精确性和正确性,以及对 Set 集合中去重功能的实现至关重要,以下 String 类中的 hashCode 方法和 equals 方法:
1 | public int hashCode() { |
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
什么是 Java 序列化和反序列化,如何实现 Java 序列化?或者请解释 Serializable 接口的作用
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间,序列化是为了解决在对对象流进行读写操作时所引发的问题。反序列话则刚好相反,将流化后的对象重新恢复成对象状态就称为反序列化。
序列化的实现:将需要被序列化的类实现 Serializable 接口,该接口没有需要实现的方法,实现 Serializable 接口只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream (对象流)对象,接着使用ObjectOutputStream 对象的 writeObject(Object obj) 方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则使用输入流。
Object 类中常见的方法,为什么 wait notify 会放在 Object 里边?##
1 | public String toString() {} |
以上便是 Object 类中所有的方法,其中 toString(), equals(), hashCode() 三个方法是我们见的相对较多的,finalize() 这个方法是由垃圾收集器在确定这个对象没有被引用时在释放对象占用的内存之前会调用该方法,clone() 方法是给对象创建一个自己的副本,前提是对象已经实现了 Cloneable 接口,否则抛出 CloneNotSupportedException,getClass() 方法返回此对象的运行时类型。
wait 有三个重载方法,同时必须捕获非运行时异常 InterruptedException。
- wait() 进入等待,需要 notify(),notifyAll() 才能唤醒
- wait(long timeout) 进入等待,经过 timeout 超时后,若未被唤醒,则自动唤醒
- wait(timeout, nanos) 进入等待,经过 timeout 超时后,若未被唤醒,则自动唤醒。相对 wait(long timeout) 时间更加精确。
wait() 和 notify() 以及 notifyAll() 则是用来控制线程的状态的,它们必须在 synchronized 同步关键字所限定的作用域中调用,否则会报错 java.lang.IllegalMonitorStateException,意思是因为没有同步,所以线程对象锁的状态是不确定的,不能调用这些方法。同时 synchronized 关键字锁可以是任意对象,任意对象调用的方法则一定是定义在 Object 类中。
wait 表示持有对象锁的线程准备释放对象锁,释放资源并进入等待状态,直到它被其他线程通过 notify() 或者 notifyAll() 唤醒。
notify 表示持有对象锁的线程准备释放对象锁,调用 notify() 通知 JVM 随机选择一个在该对象上调用 wait() 方法的线程,解除其阻塞状态使其获得对象锁,synchronized 代码作用域结束后,随机选择的那个线程获得对象锁,其他调用 wait() 方法的线程继续等待,直到有新的 notify() 或者 notifyAll() 被调用。
notifyAll 表示持有对象锁的线程准备释放对象锁,调用 notifyAll() 通知 JVM 唤醒所有在该对象上调用 wait() 方法的线程的阻塞状态, synchronized 代码作用域结束后,JVM 通过算法将对象锁指派给其中一个线程,当前获得对象锁的线程 synchronized 代码作用域结束后,然后所有被唤醒的线程不再等待,之前所有被唤醒的线程都有可能获得该对象锁权限,这个由 JVM 算法决定。