如何唯一確定一個 Java 類?

renke發表於2021-09-09

今天偶然想起之前和朋友討論過的一個問題:如何唯一確定一個 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.UniqueClassclass com.chenshuyi.UniqueClassfalse

上面的結果顯示: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 中其實是不存在的。

原文出處:https://www.cnblogs.com/chanshuyi/p/how_to_confirm_an_unique_class.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/430/viewspace-2814233/,如需轉載,請註明出處,否則將追究法律責任。

相關文章