如何唯一確定一個 Java 類?
今天偶然想起之前和朋友討論過的一個問題:如何唯一確定一個 Java 類?我相信大多數朋友遇到這個問題的回答都是:類的全路徑唄。但事實上,唯一確定一個 Java 類,單單靠類路徑是不夠的,還要多加上一個東西:類載入器。也就是說, 類載入器 + 類路徑才唯一確定一個 Java 類。
為了證明我所說的,我們來做一個簡單的實驗。
//自定義一個類載入器 ClassLoader myLoader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String fileName=name.substring(name.lastIndexOf(".")+1)+".class"; InputStream is=getClass().getResourceAsStream(fileName); if( is == null ){ return super.loadClass(name); } byte[] bytes = new byte[is.available()]; is.read(bytes); //通過自定義類載入器讀取class檔案的二進位制流 return defineClass(name, bytes, 0,bytes.length); } catch (IOException e) { e.printStackTrace(); throw new ClassNotFoundException(name); } } }; //比較類是否相同 Object obj = myLoader.loadClass("com.chenshuyi.UniqueClass").newInstance(); System.out.println(obj.getClass()); System.out.println(UniqueClass.class); System.out.println(obj instanceof UniqueClass);
在上面這段程式碼中,我首先定義了一個自定義類載入器 myLoader,之後讓其去載入 com.chenshuyi.UniqueClass
類,之後呼叫 newInstance()
獲得該類的例項 obj。
接著分別列印輸出 obj 物件的類路徑,以及 UniqueClass 類的類路徑,最後使用 instanceof 符號判斷 obj 物件是否是 UniqueClass 類的例項。最後的輸出結果是:
class com.chenshuyi.UniqueClass class com.chenshuyi.UniqueClass false
上面的結果顯示:obj 物件和 UniqueClass 類的類路徑完全相同,都是 com.chenshuyi.UniqueClass
。但是 obj 物件卻不是 UniqueClass 類的例項。 這就驗證了我的說法,即:類載入器 + 類路徑才唯一確定一個 Java 類。
其實在 Java 語言中,還有一個與之非常類似的情況: 如何唯一確定類中的一個方法? 按照我們一直以來的直覺,我們會回答:方法名、形參型別、形參個數。例如下面的兩個方法雖然方法名相同,但是引數型別和個數不同,所以他們是不同的方法。
public void Hello(String name) public void Hello(String name, int age)
但下面兩個方法雖然返回型別不同,但他們的方法名和引數型別是一致的,所以他們無法通過編譯。
public void Hello(String name) public String Hello(String name)
但是其實對於 JVM 來說,在同一個類中是可以存在方法名相同並且引數型別相同的方法名的。也就是說, 在JVM 中判斷一個方法的要素是:類名、方法名以及方法描述符。 與 Java 原始碼中的不同在於方法描述符這個概念。方法描述符由方法的引數型別和返回型別所構成。例如下面的這個方法,方法描述符就是 name 這個引數,以及 String 這個返回型別。
public String Hello(String name)
為了證明我上面的觀點,我們再做一個簡單的實驗。
下面的程式碼聲明瞭一個方法 a 和 方法 b,方法名不同,返回型別不同。
public class UniqueMethod { public void a(){} public String b(){ return "b"; } public static void main(String[] args) { System.out.println("Hello"); } }
為了證明在 JVM 對於方法唯一性判斷,我將通過修改位元組碼的方式,讓 UniqueMethod 位元組碼變成下面這樣。即有兩個相同的 a 方法,它們的方法名、形參型別、形參個數都相同,但是返回引數型別不同。
public class UniqueMethod { public void a(){} public String a(){ return "b"; } public static void main(String[] args) { System.out.println("Hello"); } }
那麼實驗開始了!
首先我們用 javac 命令編譯出位元組碼 class 檔案,接著使用 asmtools 工具將 class 檔案再轉為 jasm 檔案。我們開啟 jasm 檔案看看:
可以看到裡面有三個方法,分別是 a 方法、b 方法和 main 方法。此時我們將 b 方法名稱直接修改成 a 方法,接著使用 asmtools 工具將 jasm 檔案轉為 class 檔案。通過這種方式,我們就可以在一個類中擁有兩個名為 a 的方法了。這兩個 a 方法,它們的方法名、形參型別、形參個數都相同,但是返回引數型別不同。
生成修改後的 class 檔案之後,我們執行 java UniqueMethod
命令,順利打印出字元:Hello。這說明 class 檔案並沒有任何錯誤,JVM 對於方法名、形參型別、形參個數都相同,但是返回引數型別不同的方法,是完全接受的。
讓我們再用 javap 命令來看看 class 檔案的位元組碼結構,我們會發現確實是存在了兩個名稱為 a 的方法的。
最後讓我們來總結一下: 在 JVM 中,類路徑和類載入器唯一確定一個 Java 類,方法名、形參型別、形參個數、返回引數型別唯一確定一個 Java 類中的方法。
其實不僅僅是類與方法的唯一性,在很多方面 JVM 和 Java 語言規範真是有很大的差別。很多在 Java 中成立的東西,到了 JVM 其實就不一定成立了。例如:Java 的泛型、Java 的 lambla 表示式等等,其實只在 Java 語言層面存在,而在 JVM 中其實是不存在的。
好了,今天的分享到此為止了。喜歡這篇文章的話,那就分享給朋友一起看吧!