Object
Object类
java.lang.Object类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。在对象实例化的时候,最终找的父类就是Object
如果一个类没有特别指定父类,那么默认则继承自Object类。
1 |
|
toString
**public String toString()**:返回该对象的字符串表示。
toString方法返回该对象的字符串表示,其实该字符串内容就是对象的类型+@+内存地址值。
由于toString方法返回的结果是内存地址,而在开发中,经常需要按照对象的属性得到相应的字符串表现形式,因此也需要重写它
1 |
|
覆盖重写
如果不希望使用toString方法的默认行为,则可以对它进行覆盖重写。例如自定义的Person类:
1 |
|
equals
**public boolean equals(Object obj)**:指示其他某个对象是否与此对象“相等”
调用成员方法equals并指定参数为另一个对象,则可以判断这两个对象是否是相同的。这里的“相同”有默认和自定义两种方式
equals方法源码:
1 |
|
如果没有覆盖重写equals方法,那么Object类中默认进行==运算符的对象地址比较,只要不是同一个对象,结果必然为false
==
对于基本类型和引用类型的作用效果是不同的:
- 对于基本数据类型来说,
==
比较的是值。 - 对于引用数据类型来说,
==
比较的是对象的内存地址。
equals()
方法存在两种使用情况:
- 类没有重写
equals()
方法:通过equals()
比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是Object
类equals()
方法 - 类重写了
equals()
方法:一般我们都重写equals()
方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)
如果希望进行对象的内容比较,即所有或指定的部分成员变量相同就判定两个对象相同,则可以覆盖重写equals方法。
1 |
|
这段代码充分考虑了对象为空、类型一致等问题,但方法内容并不唯一。
Objects类中的equals方法
在JDK7添加了一个Objects工具类,它提供了一些方法来操作对象,它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象。
在比较两个对象的时候,Object的equals方法容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题。
public static boolean equals(Object a, Object b): 判断两个对象是否相等
1 |
|
hashcode
hashCode方法用来返回对象的哈希值,提供该方法是为了支持哈希表,例如HashMap,HashTable等,在Object类中的代码如下
1 |
|
这是一个native声明的本地方法,返回一个int型的整数。由于在Object中,因此每个对象都有一个默认的哈希值。
HashCode的哈希值是怎么确定的
Object
的 hashCode()
方法是本地方法,也就是用 C 语言或 C++ 实现的。
⚠️ 注意:该方法在 Oracle OpenJDK8 中默认是 “使用线程局部状态来实现 Marsaglia’s xor-shift 随机数生成“, 并不是 “地址” 或者 “地址转换而来”, 不同 JDK/VM 可能不同在 Oracle OpenJDK8 中有六种生成方式 (其中第五种是返回地址), 通过添加 VM 参数: -XX:hashCode=4 启用第五种。
对象在不重写的情况下使用的是Object的equals方法和hashcode方法,从Object类的源码我们知道,默认的equals 判断的是两个对象的引用指向的是不是同一个对象;而hashcode也是根据对象地址生成一个整数数值
- 如果两个对象的equals的结果是相等的,则两个对象的 hashCode 的返回结果也必须是相同的。
- 任何时候重写equals,都必须同时重写hashCode。
1 |
|
为什么要有 hashCode
我们以“HashSet
如何检查重复”为例子来说明为什么要有 hashCode
?
下面这段内容摘自我的 Java 启蒙书《Head First Java》:
当你把对象加入
HashSet
时,HashSet
会先计算对象的hashCode
值来判断对象加入的位置,同时也会与其他已经加入的对象的hashCode
值作比较,如果没有相符的hashCode
,HashSet
会假设对象没有重复出现。但是如果发现有相同hashCode
值的对象,这时会调用equals()
方法来检查hashCode
相等的对象是否真的相同。如果两者相同,HashSet
就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了equals
的次数,相应就大大提高了执行速度。+5
那为什么 JDK 还要同时提供equals和hashCode方法呢?
这是因为在一些容器(比如 HashMap
、HashSet
)中,有了 hashCode()
之后,判断元素是否在对应容器中的效率会更高(参考添加元素进HashSet
的过程)!
我们在前面也提到了添加元素进HashSet
的过程,如果 HashSet
在对比的时候,同样的 hashCode
有多个对象,它会继续使用 equals()
来判断是否真的相同。也就是说 hashCode
帮助我们大大缩小了查找成本。
那为什么不只提供 hashCode()
方法呢?
这是因为两个对象的hashCode
值相等并不代表两个对象就相等。
那为什么两个对象有相同的 hashCode
值,它们也不一定是相等的?
因为 hashCode()
所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓哈希碰撞也就是指的是不同的对象得到相同的 hashCode
)。
总结下来就是:
- 如果两个对象的
hashCode
值相等,那这两个对象不一定相等(哈希碰撞)。 - 如果两个对象的
hashCode
值相等并且equals()
方法也返回true
,我们才认为这两个对象相等。 - 如果两个对象的
hashCode
值不相等,我们就可以直接认为这两个对象不相等。
为什么重写 equals() 时必须重写 hashCode() 方法?
因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
如果重写 equals()
时没有重写 hashCode()
方法的话就可能会导致 equals
方法判断是相等的两个对象,hashCode
值却不相等。
思考:重写 equals()
时没有重写 hashCode()
方法的话,使用 HashMap
可能会出现什么问题。
总结:
equals
方法判断两个对象是相等的,那这两个对象的hashCode
值也要相等。- 两个对象有相同的
hashCode
值,他们也不一定是相等的(哈希碰撞)
wait,notify和notifyAll
- wait() 方法:
- 当一个线程调用某个对象的
wait()
方法时,它会释放该对象的锁,并使线程进入等待状态(WAITING状态),直到其他线程调用相同对象的notify()
或notifyAll()
方法。 - 在调用
wait()
方法之前,线程必须拥有该对象的锁,通常通过synchronized
块或方法来获得。 - 调用
wait()
方法后,线程会释放锁,使得其他线程可以进入同步块或同步方法。 - 线程在等待期间不会消耗CPU资源,因为它不会执行任何操作。
- 当一个线程调用某个对象的
- notify() 方法:
- 当一个线程调用某个对象的
notify()
方法时,它会唤醒在该对象上等待的单个线程。 - 被唤醒的线程将从等待状态变为阻塞状态,并且再次尝试获取对象的锁。
- 如果对象的锁可用,线程将进入就绪状态,等待CPU调度执行。
- 如果没有线程在该对象上等待,
notify()
方法不会做任何事情。
- 当一个线程调用某个对象的
- notifyAll() 方法:
- 类似于
notify()
,但notifyAll()
会唤醒在该对象上等待的所有线程。 - 所有被唤醒的线程将从等待状态变为阻塞状态,并尝试获取对象的锁。
- 通常,
notifyAll()
用于确保所有等待的线程都有机会重新竞争锁。
- 类似于