下面这些是 Java 中的 Object 类中方法,共 11 个,9 种方法,wait() 方法被重载了。
方法 | 描述 |
---|---|
protected native Object clone() | 创建并返回当前对象的一份拷贝 |
public boolean equals(Object obj) | 比较两个对象是否相等 |
protected void finalize() | 当对象要被垃圾回收前,调用该方法 |
public final native Class<?> getClass() | 返回该类的 Class 对象 |
public native int hashCode() | 返回该对象的哈希码值 |
public final native void notify() | 唤醒正在等待的对象监视器所在的单个线程 |
public final native void notifyAll() | 唤醒正在等待的对象监视器所在的所有线程 |
public String toString() | 返回对象的字符串表示 |
public final void wait() | 线程等待 |
public final native void wait(long timeout) | 在规定的时间内线程等待 |
public final void wait(long timeout, int nanos) | 在规定的时间内线程等待 |
我们知道 Java 的继承是单继承的,也即继承树是单根继承,树的根就是 Object 类,Java 中的所有类都直接或间接继承自 Object,无论是否明确指明,无论类是否是抽象类。Object 类可以说是 Java 类的始祖类,其中有一些方法也是预留给了后代类,也即是上面表中没有 final 关键字修饰的方法,有 clone() 方法,equals() 方法,finalize() 方法,hashCode() 方法,toString() 方法,这些都是常见的被子类重写的方法,下面就针对这些方法做一个探讨。
1、clone() 方法
从字面意上看,这个方法被设计为克隆对象,返回一个和被克隆的对象一模一样的对象。这个方法被 protect 关键字修饰,说明只能被子类重写和调用。但是在子类重写该方法时,必须要加上一个 “克隆标记” 的接口 Cloneable,这个接口里面什么方法都没有,纯粹就是一个 “标记”。这个方法被 native 关键字修饰,所以可以看出这个是一个本地方法,最终调用的是外部的链接库(C语言或C++写成),非 Java 代码实现。
下面通过实验看看 clone() 方法的真相。
public class CloneTest implements Cloneable{
public String name;
public CloneTest(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
CloneTest c1 = new CloneTest("小明");
CloneTest c2 = (CloneTest) c1.clone();
System.out.println(c1 == c2); // false
System.out.println(c1.name == c2.name); // true
}
}
代码运行的结果已经写在了代码注释中,该类没有实际重写父类中的 clone() 方法,只是简单的调用了父类的 clone() 方法。可以看到 c1 所引用的对象中 name 字段和 c2 所引用的对象的 name 字段地址相同,说明 c1.name 和 c2.name 都是对 “小明” 这一个字符串对象的引用,而并没有因克隆而产生一个新的 “小明” 字符串对象,也即是 clone() 方法本质上只是对 引用的复制(克隆),并没有真正复制对应对象中的内容,所以这只能算是一种 “浅克隆” 或者说是 “浅拷贝”。上面这段代码如果改变 c1.name 的值,c2.name 的值不会跟着改变,但如果 像下面代码这样,改变了 name 对应的值,就会对克隆对象中对应字段的值造成影响。
public class CloneTest {
static class Clothes {
public String name;
public Clothes(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
static class People implements Cloneable{
public Clothes clothes;
public People(Clothes clothes) {
this.clothes = clothes;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
People p1 = new People(new Clothes("测试"));
People p2 = (People) p1.clone();
System.out.println(p1 == p2); // false
System.out.println(p1.clothes == p2.clothes); // true
System.out.println(p1.clothes.name == p2.clothes.name); // true
p1.clothes.name = "小明";
System.out.println(p2.clothes.name); // 小明
}
}
所以要想真正实现 “ 深拷贝”,必须要递归克隆到字段不再是一个类类型的字段,对类类型也要调用对应的 clone () 方法,这样就可以保证引用字段都被复制了一遍。
上面 People 类中的 clone() 方法可以写成:
@Override
protected Object clone() throws CloneNotSupportedException {
People people = (People) super.clone();
people.clothes = (Clothes) clothes.clone();
return people;
}
2、equals() 方法
equals方法用来判断两个对象是否相等,Obejct 类中的 equals() 方法和直接使用 == 运算符是一样的,都是看引用是否相同,这点可以从源码中看出。子类可以根据自己的需要重写这个方法,并且重写该方法时遵循以下规则:
- 自反性,对象必须与自身相等,即 x.equals(x) 为 true
- 对称性,如果 x.equals(y) 为 true,那么 y.equals(x) 也为 true
- 传递性,如果 x.equals(y) 为 true,y.equals(z) 为 true,那么 x.equals(z) 也为 true
- 一致性,多次调用 equals() 方法,返回值应该始终是 true 或始终是 false
- 对于非空的 null 值,x.equals(null) 为 false
在重写 equals() 方法时,也最好一并重写 hashCode() 方法,使得当 equals() 方法返回 true 时,两个对象的 hashCode 也是相同的。当然这并不是必须的,但为什么要这样做呢?因为如果对象要存储在散列结构(如 HashTable、HashSet、HashMap)中时,判断两个对象是否相等依据是 hashCode() 方法,如果只重写了 equals() 方法,而没有重写 HashCode() 方法,就可能会出现两个对象 equals 是 true,但 hashCode 不同,造成我们认为一样的对象被重复放入这些散列结构中。使用 hashCode 比较对象的另一好处是可以提高性能和搜索效率,从原来的全部查找对比,变为只查找一次,时间复杂度从原来的 O(n) 变为了 O(1)。
注意: equals 为 true 的对象,hashCode 也是相同的,但是反过来,hashCode 相同的两个对象,equals 不一定为 true。
equals() 就是一个普通的方法,返回 true 还是 false 的决定权在于我们。
常用的实现步骤:
- 检查是否是同一个对象的引用,如果是,直接返回 true
- 检查是否是同一类型,如果不是直接,返回 false,注意这里判断是否是同一类型,不能使用 instanceof 关键字,这个关键字判断的是前面对象是否是后面类的实例或者子类的实例额,不够准确。
- 将 Object 对象转型
- 判断每个关键域值是否相等(业务的实际需要)
实例代码如下(来自 GitHub):
public class EqualExample {
private int x;
private int y;
private int z;
public EqualExample(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EqualExample that = (EqualExample) o;
if (x != that.x) return false;
if (y != that.y) return false;
return z == that.z;
}
}
3、finalize() 方法
虽然这个方法也可以被子类重写,但是极少被使用,这个方法和 C++ 的析构函数功能是不一样的,C++ 中使用析构函数来清除一个对象,而 Java 中清除对象的工作是由 Java 虚拟机帮我们完成的。Java 中设计这个方法只是想在垃圾回收器将对象从内存中清除前做一些其他自定义的清理工作,在未来的 JDK 版本中,这个方法很有可能会被取消。在 Java 中,与 finalize() 方法相似的有 finally 语句块,用来在异常发生后关闭一些资源(如文件),常和 try .. catch 语句结合使用。final 关键字和 finalize() 看上去也很相似,但是一点关系都没有,final 关键词可以用来修饰变量、属性、方法和类,分别表示常量、属性不可变、方法不可被重写、类不可被继承。
4、hashCode() 方法
hashCode() 方法当然就是用来返回一个对象的哈希码(hashCode)啦,hashCode 是一个 int 类型的数字,如果我们没有重写一个类的 toString() 方法,而使用 System.out.println 打印这个类,调用的就是 Object 类中的 toString() 方法,输出格式为 类名@hashCode的十六进制数 ,其实 hashCode() 方法完全不用手写,强大的 IDE 工具一般都有自动生成的功能,可以帮我们写 hashCode() 方法和 equals() 方法,另外在 JDK7 中新增的 Objects 类,也有个静态方法 hashCode(obj) 可以生成对象的 hashCode 值。
5、toString() 方法
这个方法就不多说了,相信都懂,通俗的说就是对象的自我介绍方法,嘿嘿。
觉得文章还不错,可以关注 编程心路 微信公众号,回复任意关键字有惊喜!在编程的路上,我们一起成长。
编程心路
网友评论