通過重寫 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;
if (this == o) return true;
首先使用 == 直接判斷兩個對象的記憶體位址是否相同,如果相同直接傳回 true。我自己重寫 equals 方法時,就完全沒想到用 == 判斷,實際上這個 if 可以大大提高效率。
2. if (!(o instanceof EqualsFather)) return false;
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…