Java基礎--關於Object的一些通用方法
equals(Object obj): 判斷兩對象是否相同(String類重寫了該方法)
1. equals() 與 == 的區別
- 對於基本類型,== 判斷兩個值是否相等,基本類型沒有 equals() 方法。
- 對於引用類型,== 判斷兩個實例是否引用同一個對象,而 equals() 判斷引用的對象是否等價。
Integer x = new Integer(1); Integer y = new Integer(1); System.out.println(x.equals(y)); // true System.out.println(x == y); // false
2.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比較:對於任何非空對象x x.euqals(null); // false;
3.實現
- 檢查是否為同一個對象的引用,如果是直接返回 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; } @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; } }
hashCode():返回對象的散列值
hashCode()是一個native方法,而且返回值類型是整型;實際上,該native方法將對象在內存中的地址作為哈希碼返回,可以保證不同對象的返回值不同。相同散列值不等於實例相同,而兩相等的實例散列值必相同。
JDK中對hashCode()方法的作用,以及實現時的註意事項做了說明:
-
hashCode()在哈希表中起作用,如java.util.HashMap;
-
如果對象在equals()中使用的信息都沒有改變,那麽hashCode()值始終不變;
-
如果兩個對象使用equals()方法判斷為相等,則hashCode()方法也應該相等;
-
如果兩個對象使用equals()方法判斷為不相等,則不要求hashCode()也必須不相等;但是開發人員應該認識到,不相等的對象產生不相同的hashCode可以提高哈希表的性能。
hashCode()的作用:
總的來說,hashCode()在哈希表中起作用,如HashSet、HashMap等。
當我們向哈希表(如HashSet、HashMap等)中添加對象object時,首先調用hashCode()方法計算object的哈希碼,通過哈希碼可以直接定位object在哈希表中的位置(一般是哈希碼對哈希表大小取余)。如果該位置沒有對象,可以直接將object插入該位置;如果該位置有對象(可能有多個,通過鏈表實現),則調用equals()方法比較這些對象與object是否相等,如果相等,則不需要保存object;如果不相等,則將該對象加入到鏈表中。
這也就解釋了為什麽equals()相等,則hashCode()必須相等。如果兩個對象equals()相等,則它們在哈希表(如HashSet、HashMap等)中只應該出現一次;如果hashCode()不相等,那麽它們會被散列到哈希表的不同位置,哈希表中出現了不止一次。
實際上,在JVM中,加載的對象在內存中包括三部分:對象頭、實例數據、填充。其中,對象頭包括指向對象所屬類型的指針和MarkWord,而MarkWord中除了包含對象的GC分代年齡信息、加鎖狀態信息外,還包括了對象的hashcode;對象實例數據是對象真正存儲的有效信息;填充部分僅起到占位符的作用, 原因是HotSpot要求對象起始地址必須是8字節的整數倍。
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():Object 的受保護方法,這意味著,如果一個類不顯式去重寫clone() 就沒有這個方法。
1.cloneable
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‘
接下來覆蓋 Object 的 clone() 得到以下實現:
public class CloneExample { private int a; private int b; @Override protected 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
以上代碼拋出CloneNotSupported異常,因為CloneExample 沒有實現 Cloneable 接口。
public class CloneExample implements Cloneable { private int a; private int b; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
應該註意的是,clone() 方法並不是 Cloneable 接口的方法,而是 Object 的一個 protected 方法。Cloneable 接口只是規定,如果一個類沒有實現 Cloneable 接口又調用了 clone() 方法,就會拋出 CloneNotSupportedException。
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]; } @Override protected 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(e2.get(2)); // 222
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]; } @Override protected 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(e2.get(2)); // 2
使用 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(e2.get(2)); // 2
Java基礎--關於Object的一些通用方法