天天看點

清晰了解java的instanceof關鍵字

通過重寫 Object 的 equals 方法開始本文 ( instanceof 詳解跳轉)

由于 Object 類中 equals 方法實際上就是 == ,是以大部分類都按需重寫了 equals 方法。

public class EqualsFather {
    private int father;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof EqualsFather)) return false;
        EqualsFather that = (EqualsFather) o;
        return father == that.father;
    }

    @Override
    public int hashCode() {
        return Objects.hash(father);
    }
}
-------------------------------------------
public class EqualsSon extends EqualsFather{
    private int son;
}
           

以上是使用 idea 自動生成的 equals 及 hashCode 方法,雖然說平時我們隻需要動動手指生成一下就完事了,但不能知其然而不知其是以然。

1.

if (this == o) return true;

首先使用 == 直接判斷兩個對象的記憶體位址是否相同,如果相同直接傳回 true。我自己重寫 equals 方法時,就完全沒想到用 == 判斷,實際上這個 if 可以大大提高效率。

2.

if (!(o instanceof EqualsFather)) return false;

instanceof 是 java 的保留關鍵字,跟 ==,<,> 類似,是比較運算符。

instanceof 的作用是,判斷左側的對象對應的類是否為右側類 / 接口的子類 / 實作類,并且是否被同一個類加載器加載。如果不是,或者左側為 null,輸出 false;否則輸出 true。

EqualsFather father = new EqualsFather();
EqualsSon son = new EqualsSon();
例子1. System.out.println(son instanceof EqualsFather);
例子2. System.out.println(son instanceof ArrayList);
           

例子 1 的輸出毫無疑問是 true 的,因為 EqualsSon 是 EqualsFather 的子類。

例子 2 輸出什麼呢? false ?其實根本無法編譯通過,并且會提示

cannot cast 'EqualsSon' to 'java.util.ArrayList'

通過僞代碼來示範 instanceof 的判斷機制

boolean res;
if(obj == null){
    //如果為 null 
    res = false;
}else{
    try{
        obj = (ArrayList) obj;
        res = true;
    }catch(ClassCastException e){
        res = false;
    }
}
return res;
           
jvm 是如何執行 instanceof 指令的呢?

son instanceof EqualsFather

1.通過 son.getClass() 得到的 class 對象,找到方法區中對應的類型資訊。

2.通過 EqualsFather.class 得到的 class 對象,找到方法區中對應的類型資訊。

3.通過 java 規範判斷兩個類型是否為繼承/實作關系。

測試:不同的類加載器加載同一個 .class 檔案,檢視 instanceof 結果

public class EqualsTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader myClassLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String filename = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = this.getClass().getResourceAsStream(filename);
                    if (is == null){
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name,b,0,b.length);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return super.loadClass(name);
            }
        };
        Object o = myClassLoader.loadClass("object.equals.EqualsSon").newInstance();
        Object o1 = myClassLoader.loadClass("object.equals.EqualsFather").newInstance();
        EqualsSon son = new EqualsSon();
        System.out.println(o.getClass());
        System.out.println(son.getClass().equals(o.getClass()));
        System.out.println(o instanceof object.equals.EqualsSon);
        System.out.println(o instanceof object.equals.EqualsFather);
    }
}
           

o 為自定義類加載器加載 object.equals.EqualsSon 的 class 檔案後生成的對象,o1 同理。

son 為應用程式類加載器加載 object.equals.EqualsSon 的 class 檔案後生成的對象。

輸出1:

Object o1 = myClassLoader.loadClass("object.equals.EqualsFather").newInstance();

編譯通過,但運作報錯:JVM 抛出

java.lang.LinkageError: attempted duplicate class definition for name:

。LinkageError 表示類加載器多次加載同一個 class 檔案。

是由于 EqualsSon 繼承了 EqualsFather,當自定義類加載器加載 EqualsSon ,并調用 Class::newInstance() 方法通過 EqualsSon 的無參構造建立 EqualsSon 對象時,觸發了父類 EqualsFather 的初始化(主動引用)。當然在 EqualsFather 被初始化前,它的加載、驗證、準備階段已經完成。

如果再讓自定義加載器加載 EqualsFather ,那麼就不是第一次加載了,一定會抛出 LinkageError。

ClassLoader classLoader = EqualsTest.class.getClassLoader();
classLoader.loadClass("object.equals.EqualsSon");
classLoader.loadClass("object.equals.EqualsFather");
           

這樣就不會報錯,因為 loadClass 方法隻是讓類加載器加載 EqualsSon 類而已。沒有觸及初始化階段,不會觸發父類的加載。

輸出2:

System.out.println(o.getClass());

輸出

class object.equals.EqualsSon

不同的類加載器加載的是同一個 class 檔案。

輸出3:

System.out.println(son.getClass().equals(o.getClass()));

對于任意一個類,都必須由加載它的類加載器和這個類本身一起共同确立其在 Java 虛拟機中的唯一性。每一個類加載器,都有一個獨立的類名稱空間。比較兩個類是否 “相等”,隻有在這兩個類是由同一個類加載器加載的前提下才有意義。否則,即使這兩個類來自同一個 Class 檔案,被同一個 Java 虛拟機加載,隻要加載他們的類加載器不同,那這兩個類就必定不相等。

------《深入了解 Java 虛拟機》周志明

兩個不同的類加載器,加載同一個 Class 檔案,會生成兩個不同的 Class 對象。是以輸出 false

輸出4:

System.out.println(o instanceof object.equals.EqualsSon);

現在堆中有兩個 EqualsSon類 對象,一個由自定義類加載器加載,一個由 ApplicationClassLoader 加載。

輸出 false

輸出5:

System.out.println(o instanceof object.equals.EqualsFather);

o 對應的 EqualsSon類 與被同一個類加載器加載的 EqualsFather類為繼承關系。但與被 ApplicationClassLoader 加載的 EqualsFather類沒有繼承關系。

輸出 false

3.通過成員變量判斷兩個相同類型的對象是否相等

EqualsFather that = (EqualsFather) o;
    return father == that.father;
           

end…