java class的相容問題,javaclass相容

來源:互聯網
上載者:User

java class的相容問題,javaclass相容

    前不久在工作中,遇到了幾次編譯class引起的NoSuchMethodError,經過分析與測實驗證,也算是搞清楚了中間的來龍去脈,現在把一些結論性的東西(附帶一些過程性的分析)分享出來。

    在使用javac -source 1.6 -target 1.6來編譯低版本的(這裡為1.6)class時,記得要使用-bootclasspath參數來指定1.6版本的類庫(一般是rt.jar),不指定的話,會產生一個警告:

警告: [options] 未與 -source 1.6 一起設定引導類路徑

或者英文版的

warning: [options] bootstrap class path not set in conjunction with -source 1.6

    如果忽視這個警告(當時我在網上搜尋上述中文警告時,沒有任何資料說需要引起注意,以及該如何解決),編譯出來的class可能無法在低版本的jre中運行,假如源碼中調用了一些特殊方法,則會在執行時拋出NoSuchMethodError。比如ConcurrentHashMap的keySet方法,在jdk1.6中,該方法返回的是Set,在jdk1.8中,該方法返回的是KeySetView,它是jdk1.8中新增的一個類,為Set的一個實現。當把這樣編譯出來的class放到jre1.6中去運行時,會因為找不到傳回型別為KeySetView的keySet方法而拋出NoSuchMethodError,雖然編譯後的class的版本是1.6。

    基於上面的認知,來討論一下如下情境
    現在有apiA_1.0.jar與apiB_1.0.jar,apiB_1.0.jar依賴apiA_1.0.jar,前者是基於後者編譯的,也就是這兩個版本之間不存在相容問題。
    然後假如apiA進行了修改,升級為apiA_1.1.jar,其中某個類的某個方法的傳回值由Object改為了String(從源碼上來講,這樣改是相容的,因為String是一個Object,這應該就是裡氏替換吧),此時apiB_1.0.jar就不相容apiA_1.1.jar了,如果單方面把apiA升級到1.1,apiB在調用apiA中的那個傳回值為Object的方法時,會因為找不到方法而拋出NoSuchMethodError(如果對此有異議,請看後文),因為現在在apiA中,只有那個傳回值為String的方法了,並且,你也不可能保留傳回值為Object的那個方法,它們是互相衝突的。
    當然,此時也可以重新發布一個apiB_1.1.jar,基於apiA_1.1.jar編譯出來的版本。但這樣,也就意味著,apiB依賴了apiA特定的版本,這樣非常不利於依賴維護,使用過程中很容易出問題,而且這種問題只有在運行時,調用了有問題的方法時才會發現,應用程式的編譯過程中是不會報錯的(apiA和apiB是已經編譯的jar了)。

    也許此時你已經注意到了,難道jdk也不向前相容了?為什麼我用jdk1.6編譯出來的程式能在jre1.8中正常的調用ConcurrentHashMap.keySet?它不是也存在上面所說的問題嗎?它為什麼不會因為找不到傳回值為Set的keySet方法而拋異常?
    這裡就需要介紹一下class中的橋接方法(bridge method)了,它不報錯,是因為1.8中確實也存在一個傳回值為Set的keySet方法,只不過不是存在於源檔案中,而是存在於class檔案中,通過javap -v java.util.concurrent.ConcurrentHashMap反編譯1.8的ConcurrentHashMap,可以看到一個傳回值為java.util.Set的keySet方法:

public java.util.Set keySet();descriptor: ()Ljava/util/Set;flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETICCode:stack=1, locals=1, args_size=10: aload_01: invokevirtual #838 // Method keySet:()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;4: areturnLineNumberTable:line 267: 0

ps:flag參數中的ACC_BRIDGE表明了這是一個橋接方法

    雖然java文法層面不允許存在僅傳回值不同的兩個方法,但在class檔案中,並沒有此限制,在此橋接方法中,調用了傳回值為KeySetView的keySet方法。另外java.lang.reflect.Method.isBridge()就是指的這個。

    那為什麼ConcurrentHashMap.keySet會有橋接方法呢?其實也不是jdk給自己搞的特殊化,是因為keySet是一個重寫方法(介面方法也有此效果),重寫了父類AbstractMap的public Set<K> keySet()方法,這個大致可以理解為,父類或介面已經對外宣稱了該方法(也就是返回Set),那如果子類或實現者自己返回了其它子類型,那麼編譯器就得來做這個相容性工作,即建立橋接方法。如果直接改了頂層方法,編譯器自然不可能去做這個事情,它怎麼知道要跟誰相容?同理,靜態方法也會有問題。

    最後總結一下:

  • 如果你要保持跟以前的版本相容,除了介面方法或重寫父類方法,其它時候就不要改變傳回值類型,否則就不相容了。(我認為在參與開源項目時尤其需要注意這一點)
  • 使用高版本javac配置source、target參數來編譯低版本class或打jar包時,必需用bootclasspath指定對應低版本的類庫,否則也可能產生不相容。這也意味著:不要僅僅裝一個jdk8就期待編譯出一定能在jre1.6上正常啟動並執行程式,你還需要一個1.6版本的java類庫來完成編譯。
  • 如果有替換個別class檔案來打補丁的習慣,那麼也需要特別小心相容問題,原理是一樣的。

上面所說的不相容問題,會延後到真正調用問題方法時候才會暴露,所以值得加以重視。

相關連結:

javac官方文檔:http://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.