java基礎學習(3)-Object常用方法

java基礎學習(3)-Object常用方法

概覽

public native int hashCode()

public boolean equals(Object obj)

protected native Object clone() throws CloneNotSupportedException

public 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 InterruptedException

public final void wait(long timeout, int nanos) throws InterruptedException

public final void wait() throws InterruptedException

equals()

1. 等價關係

兩個對象具有等價關係,需要滿足以下五個條件:

  • 自反性
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;

2.等價與相等

  • 對於基本類型,== 判斷兩個值是否相等,基本類型沒有 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

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() 返回哈希值,而 equals() 是用來判斷兩個對象是否等價。等價的兩個對象散列值一定相同,但是散列值相同的兩個對象不一定等價,這是因爲計算哈希值具有隨機性,兩個值不同的對象可能計算出相同的哈希值。

在覆蓋 equals() 方法時應當總是覆蓋 hashCode() 方法,保證等價的兩個對象哈希值也相等。

HashSet 和 HashMap 等集合類使用了 hashCode() 方法來計算對象應該存儲的位置,因此要將對象添加到這些集合類中,需要讓對應的類實現 hashCode() 方法。

下面的代碼中,新建了兩個等價的對象,並將它們添加到 HashSet 中。我們希望將這兩個對象當成一樣的,只在集合中添加一個對象。但是 EqualExample 沒有實現 hashCode() 方法,因此這兩個對象的哈希值是不同的,最終導致集合添加了兩個等價的對象。

EqualExample e1 = new EqualExample(1, 1, 1);
EqualExample e2 = new EqualExample(1, 1, 1);
System.out.println(e1.equals(e2)); // true
HashSet<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,編譯器會自動進行這個優化。(原文補充:JVM通過移位和減法完成了乘法的操作,對性能進行優化)

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + x;
    result = 31 * result + y;
    result = 31 * result + z;
    return result;
}

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()

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;

    @Override
    public 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;

    @Override
    public 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];
    }

    @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

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];
    }

    @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

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(e2.get(2)); // 2
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章