1. 程式人生 > 實用技巧 >Java 基礎之 Object

Java 基礎之 Object

方法摘要

public final native Class<?> getClass() //獲取物件的執行時物件的類

public native int hashCode()        //獲取物件的 hash 值

public boolean equals(Object obj)   //比較兩個物件是否相等

protected native Object clone()     //建立並返回一個物件的拷貝

public String toString()    //返回物件的字串表示形式。 

public final native void notify()   //喚醒在該物件上等待的某個執行緒

public final native void notifyAll()    //喚醒在該物件上等待的所有執行緒

public final void wait()    //讓當前執行緒進入等待狀態。直到其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 方法。 

public final native void wait(long timeout)  //讓當前執行緒處於等待(阻塞)狀態,直到其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 方法,或者超過引數設定的timeout超時時間。 

public final void wait(long timeout, int nanos)  //與 wait(long timeout) 方法類似,多了一個 nanos 引數,這個引數表示額外時間(以納秒為單位,範圍是 0-999999)。 所以超時的時間還需要加上 nanos 納秒。。 

protected void finalize()   //當 GC (垃圾回收器)確定不存在對該物件的有更多引用時,由物件的垃圾回收器呼叫此方法。

equals()

等價關係

  • 自反性
x.equals(x) //true
  • 對稱性
 x.equals(y) == y.equals(x) //true
  • 傳遞性
x.equals(y) //true
y.equals(z) //true
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)); // true
System.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;
    }

    @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 用於獲取物件的 hash 值,表示在雜湊表中的位置。

equals() 方法判斷兩個物件是否等價,等價的兩個物件雜湊值一定相等,而雜湊值相等的兩個物件不一定等價,因為計算雜湊值具有隨機性。

在覆蓋 object 的 equals() 方法時應當總是覆蓋 hashCode() 方法,保證等價的兩個物件雜湊值也是相等的

String 和 ArrayList 類都重寫了 Object 的 equals() 方法,並重寫了 hashCode() 方法

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }   
    
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;
    
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }    
}

重寫 hashCode 方法時,需要用素數31作為乘子,因為31可以被 JVM 優化

  • 左移 << : 左邊的最高位丟棄,右邊補全0(把 << 左邊的資料*2的移動次冪)。
  • 右移 >> : 把>>左邊的資料/2的移動次冪。
  • 無符號右移 >>> : 無論最高位是0還是1,左邊補齊0。

所以 :31 * i = (i << 5) - i(左邊 31 * 2=62,右邊2 * 2^5-2=62)兩邊相等,JVM就可以高效的進行計算

toString

toString() 方法用於返回物件的字串表示形式。預設返回格式:物件的 class 名稱 + @ + hashCode 的十六進位制字串。

   public static void main(String[] args) {
       A a = new A();
       System.out.println(a.toString());    //client.A@b4c966a
   }

clone()

1. cloneable

clone() 是 Object 的 protected 方法,它不是 public,一個類不顯式去重寫 clone(),其它類就不能直接去呼叫該類例項的 clone() 方法。

public class A {

   public static void main(String[] args) {
       A a = new A();
       a.clone();   //Unhandled exception: java.lang.CloneNotSupportedException
   }
}

重寫 clone() 方法,必須實現 Cloneable,因為 Cloneable 介面規定,如果一個類沒有實現 Cloneable 介面又呼叫了 clone() 方法,就會丟擲 java.lang.CloneNotSupportedException

public class A implements Cloneable {

    private int a;
    private int b;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        A a = new A();
        a.clone();
    }
}

2. 淺拷貝

拷貝物件和原始物件的引用型別引用同一個物件。

public class A implements Cloneable {

    private int arr;

    public int getArr() {
        return arr;
    }

    public void setArr(int arr) {
        this.arr = arr;
    }

    @Override
    public A clone() throws CloneNotSupportedException {
        return (A) super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        A a = new A();
        a.setArr(123);
        A a1 = null;
        a1 = a.clone();
        System.out.println(a1.getArr()); //123
    }
}

3. 深拷貝

拷貝物件和原始物件的引用型別引用不同物件。

public class A implements Cloneable {

    private int arr;

    public int getArr() {
        return arr;
    }

    public void setArr(int arr) {
        this.arr = arr;
    }

    @Override
    public A clone() throws CloneNotSupportedException {
        A result = (A) super.clone();
        result.arr = this.arr;
        return result;
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        A a = new A();
        a.setArr(123);
        A a1 = null;
        a1 = a.clone();
        System.out.println(a1.getArr()); //123
    }
}

4. clone() 的替代方案

使用 clone() 方法來拷貝一個物件即複雜又有風險,它會丟擲異常,並且還需要型別轉換。Effective Java 書上講到,最好不要去使用 clone(),可以使用拷貝建構函式或者拷貝工廠來拷貝一個物件。

public class A {

    private int arr;

    public int getArr() {
        return arr;
    }

    public void setArr(int arr) {
        this.arr = arr;
    }

    public A() {
    }

    public A(A a) {
        this.arr = a.arr;
    }

    public static void main(String[] args){
        A a = new A();
        a.setArr(123);
        A a1 = new A(a);
        System.out.println(a1.getArr());    //123
    }
}