如何唯一確定一個 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 中其實是不存在的。

好了,今天的分享到此為止了。喜歡這篇文章的話,那就分享給朋友一起看吧!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM