Object 类中的方法一览:
public native int hashCode()public boolean equals(Object obj)protected native Object clone() throws CloneNotSupportedExceptionpublic String toString()public final native Class<?> getClass()protected void finalize() throws Throwable {}public final native void notify()public final native void notifyAll()public final native void wait(long timeout) throws InterruptedExceptionpublic final void wait(long timeout, int nanos) throws InterruptedExceptionpublic final void wait() throws InterruptedException
*equals()
等价关系
- 自反性
x.equals(x); // true - 对称性
x.equals(y) == y.equals(x); // true - 传递性
if(x.equals(y) && y.equals(z)) x.equals(z); // true - 一致性 多次调用 equals() 方法结果不变
x.equals(y) == x.equals(y); // true - 与 null 的比较 对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false
x.equals(null); // false
- 自反性
等价与相等
- 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
- 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。
Integer x = new Integer(1);Integer y = new Integer(1);System.out.println(x.equals(y)); // trueSystem.out.println(x == y); // false
- 实现
- 检查是否为同一个对象的引用,如果是直接返回 true;
- 检查是否是同一个类型,如果不是,直接返回 false;
- 将 Object 对象进行转型;
- 判断每个关键域是否相等。
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;}@Overridepublic boolean equals(Object o) {if (this == o) return true; //检查是否为同一个对象的引用,如果是直接返回 true;if (o == null || getClass() != o.getClass()){//检查是否是同一个类型,如果不是,直接返回 falsereturn false;}// 将 Object 对象进行转型EqualExample that = (EqualExample) o;// 判断每个关键域是否相等。if (x != that.x) return false;if (y != that.y) return false;return z == that.z;}}
*hashCode()
hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。 等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。 我们希望将这两个对象当成一样的,只在集合中添加一个对象,但是因为 EqualExample 没有实现 hasCode() 方法, 因此这两个对象的散列值是不同的,最终导致集合添加了两个等价的对象。
EqualExample e1 = new EqualExample(1, 1, 1);EqualExample e2 = new EqualExample(1, 1, 1);System.out.println(e1.equals(e2)); // trueHashSet<EqualExample> set = new HashSet<>();set.add(e1);set.add(e2);System.out.println(set.size()); // 2
理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。 这就要求了散列函数要把所有域的值都考虑进来。 可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。 R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。
一个数与 31 相乘可以转换成移位和减法:31*x == (x<<5)-x,编译器会自动进行这个优化。
@Overridepublic int hashCode() {int result = 17;result = 31 * result + x;result = 31 * result + y;result = 31 * result + z;return result;}
了解:IDEA中 Alt+Insert 快捷键就可以快速生成 hashCode() 和 equals() 方法。
toString()
默认返回 ToStringExample@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。
public class ToStringExample {private int number;public ToStringExample(int number) {this.number = number;}}ToStringExample example = new ToStringExample(123);System.out.println(example.toString());//输出结果:ToStringExample@4554617c
clone()
clone() 是 Object 的 protected 方法,属于浅拷贝
1. Cloneable
clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
public class CloneExample {private int a;private int b;}CloneExample e1 = new CloneExample();// CloneExample e2 = e1.clone();// 'clone()' has protected access in 'java.lang.Object'
重写 clone() 得到以下实现:
public class CloneExample {private int a;private int b;// CloneExample 默认继承 Object@Overridepublic CloneExample clone() throws CloneNotSupportedException {return (CloneExample)super.clone();}}CloneExample e1 = new CloneExample();try {CloneExample e2 = e1.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}java.lang.CloneNotSupportedException: CloneExample
以上抛出了 CloneNotSupportedException,这是因为 CloneExample 没有实现 Cloneable 接口。
应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。
Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。
public class CloneExample implements Cloneable {private int a;private int b;@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone();}}
2. 浅拷贝
- 拷贝对象和原始对象的引用类型引用同一个对象。
- 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。
- 浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
public class ShallowCloneExample implements Cloneable {private int[] arr;public ShallowCloneExample() {arr = new int[10];for (int i = 0; i < arr.length; i++) {arr[i] = i;}}public void set(int index, int value) {arr[index] = value;}public int get(int index) {return arr[index];}@Overrideprotected ShallowCloneExample clone() throws CloneNotSupportedException {return (ShallowCloneExample) super.clone();}}// 拷贝对象和原始对象的引用类型引用同一个对象。ShallowCloneExample e1 = new ShallowCloneExample();ShallowCloneExample e2 = null;try {e2 = e1.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}e1.set(2, 222);System.out.println(e1.get(2)); // 222System.out.println(e2.get(2)); // 222
3. 深拷贝
- 拷贝对象和原始对象的引用类型引用不同对象。
- 深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
- 深拷贝把要复制的对象所引用的对象都复制了一遍。
public class DeepCloneExample implements Cloneable {private int[] arr;public DeepCloneExample() {arr = new int[10];for (int i = 0; i < arr.length; i++) {arr[i] = i;}}public void set(int index, int value) {arr[index] = value;}public int get(int index) {return arr[index];}@Overrideprotected DeepCloneExample clone() throws CloneNotSupportedException {DeepCloneExample result = (DeepCloneExample) super.clone();// 创建新对象result.arr = new int[arr.length];for (int i = 0; i < arr.length; i++) {result.arr[i] = arr[i];}return result;}}DeepCloneExample e1 = new DeepCloneExample();DeepCloneExample e2 = null;try {e2 = e1.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}e1.set(2, 222);System.out.println(e1.get(2)); // 222System.out.println(e2.get(2)); // 2
4. clone() 的替代方案
使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。 Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。
public class CloneConstructorExample {private int[] arr;public CloneConstructorExample() { //构造函数arr = new int[10];for (int i = 0; i < arr.length; i++) {arr[i] = i;}}public CloneConstructorExample(CloneConstructorExample original) { // 拷贝构造函数arr = new int[original.arr.length];for (int i = 0; i < original.arr.length; i++) {arr[i] = original.arr[i];}}public void set(int index, int value) {arr[index] = value;}public int get(int index) {return arr[index];}}CloneConstructorExample e1 = new CloneConstructorExample();CloneConstructorExample e2 = new CloneConstructorExample(e1);e1.set(2, 222);System.out.println(e1.get(2)); // 222System.out.println(e2.get(2)); // 2
