jdk版本不同引起的問題分析

JDK版本不同導致的運行時錯誤

JDK版本不同導致的運行時錯誤

   最近有一同事編寫的java程序在本地開發環境中能夠正常運行,但是複製到實際環境中運行時報錯(開發環境操作系統windows,程序實際運行環境linux),異常信息如下:

 

java.lang.NoSuchMethodError: java.lang.StringBuffer: method insert(ILjava/lang/CharSequence;)Ljava/lang/StringBuffer; not found

at FirstApp.caozuoqueren$5.actionPerformed(caozuoqueren.java:393)

at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1815)

at javax.swing.AbstractButton$ForwardActionEvents.actionPerformed(AbstractButton.java:1868)

 

同事不知問題出在哪裏,讓我幫調試一下。從異常信息來看提示信息非常明確,即StringBuffer類中不存在方法insert(ILjava/lang/CharSequence;),既然程序從本地能正常編譯和運行,而換一個環境就不能正常運行,很直觀的就想到是不是由於JDK版本差異導致的問題,於是向同事詢問情況瞭解到開發時使用的JDK版本爲1.5,而實際生成環境中使用的是JDK1.4,由此基本上可以斷定是由於JDK版本不同引起的該問題,但具體原因是什麼哪?當然還要看程序代碼是如何寫的,程序中有這麼一句代碼:

sbDispInfor.insert(str.length(),sbInput.toString());

其中sbDispInforsbInput的類型都爲StringBuffer,問題就出在對insert方法的調用。於是打開JDK1.4的幫助手冊,StringBuffer類的所有insert方法定義如下:

 

而在JDK1.5中StringBuffer類的所有insert方法定義如下:

 

 

 

對比兩個版本StringBuffer類insert方法定義會發現1.5版本中比1.4版本中多了兩個方法:

StringBuffer insert(int dstOffset, CharSequence s)

StringBuffer insert(int dstOffset, CharSequence s, int start, int end)

 

代碼中調用insert方法時第2個參數爲sbInput,類型爲StringBuffer,但是1.4和1.5版本中都沒有定義第2個參數類型爲StringBuffer的insert方法,程序是如何編譯通過的哪?別忘了java支持對象類型之間的自動轉換,在1.5版本中StringBuffer的聲明如下:

public final class StringBuffer
extends Object
implements Serializable, CharSequence

 

可以看到StringBuffer類實現了CharSequence接口,因此一個StringBuffer對象其實也可以當作一個CharSequence對象來看待(java的多態性),在使用jdk1.5版本編譯程序時由於該版本中StringBuffer類中存在方法insert(int dstOffset, CharSequence s)的定義,因此javac編譯程序會把參數sbInput的類型由StringBuffer自動轉換成CharSequence,這在1.5版本中運行時是沒問題的,但是移植到1.4版本中運行時由於StringBuffer類沒有定義參數爲CharSequence類型的insert方法,因此會報本文開頭出給出的異常。

 

類似的BigDecimal類也存在類似問題,編寫一測試程序如下:

import java.math.*;

 

public class TestBigDecimal{

       public static void main( String[] args){

              try{

                BigDecimal bd1 = new BigDecimal("1");

                System.out.println(" bd1 , BigDecimal(/"1/")=" + bd1 );

         }catch(Exception e){

             System.out.println(e);

         }

             

              try{       

                BigDecimal bd2 = new BigDecimal(2);

                System.out.println(" bd2 , BigDecimal(2)=" + bd2 );

              }catch(Exception e){

                     System.out.println(e);

         }

       }

}

 

A. 使用jdk1.5進行編譯,  javac TestBigDecimal.java

然後在1.4版本下運行會拋出一下異常:

    Exception in thread "main" java.lang.UnsupportedClassVersionError: TestBigDecimal (Unsupported major.minor version 49.0)

。。。

B.使用jdk1.5進行編譯,添加source參數,javac –source  1.4  TestBigDecimal.java

   然後在1.4版本下運行會拋出以下異常:

bd1 , BigDecimal("1")=1

Exception in thread "main" java.lang.NoSuchMethodError: java.math.BigDecimal.<init>(I)V

        at TestBigDecimal.main(TestBigDecimal.java:13)

。。。

 

C.使用jdk1.5進行編譯,添加source和target參數,javac –source 1.4 –target 1.4 TestBigDecimal.java

然後在1.4版本下運行會拋出以下異常:

bd1 , BigDecimal("1")=1

Exception in thread "main" java.lang.NoSuchMethodError: java.math.BigDecimal.<init>(I)V

        at TestBigDecimal.main(TestBigDecimal.java:13)

。。。

 

分析上面的各種情況產生的結果,在A這種情況下由於是在1.5版本中編譯成的 class文件,放到1.4版本下運行時不受支持,即1.4版本不認可1.5版本生成的 class文件 類結構(很容易理解,軟件版本的向下兼容性)。在B這種情況下,通過添加參數source對於生成的class文件提供與1.4版本的源兼容性,可以看到字節碼文件可以在1.4下運行,第1個輸出語句成功執行,第2個再構造BigDecimal bd2 = new BigDecimal(2); 對象時拋出異常。在C這種情況下除了添加source參數外還添加了target參數用於生成特定 VM 版本的類文件,但是這種情況下和B報錯一樣(這地方有些疑惑,按照javac中對target參數的說明,編譯時就應該和使用1.4編譯器的效果是完全一樣的,但從結果看並非如此)。產生這種錯誤的原因是兩個版本中BigDecimal類構造器的差異導致,BigDecimal類在1.4版本中構造函數其中的兩個:BigDecimal(double val)和 BigDecimal(String val),在1.5版本當中除了上述的兩個之外又新增加了BigDecimal(int val),程序中BigDecimal bd2 = new BigDecimal(2); 聲明在1.5版本下參數‘2’實際上被當成了int型處理,如果在1.4版本下編譯文件‘2’會被轉化成‘2.0’即double型。如果用1.5編譯器編譯實際上只是標記該類可以在1.4版本中運行,編譯成的字節碼仍然是1.5編譯器的字節碼(2並沒有轉化成double型2.0),因此在1.4下面運行會出錯。

 

從以上兩個例子的分析可以得出一下結論:

1)軟件版本一般是向下兼容的,java虛擬機也不例外,即低版本虛擬機生成的class文件可以在高版本虛擬機中運行,反之則未必可以(向上兼容)。

2)在1.5版本下編譯的class文件要想在1.4版本中運行,使用javac編譯時需要添加額外的參數,如上例在1.5版本下編譯時使用命令:javac -source 1.4 TestBigDecimal.java,這樣生成的class文件能夠被1.4版本接受,但並不代表一定能夠運行(如上例就拋出異常)。

3)爲了提高軟件的可移植性,儘量使用低版本編譯類文件,這樣既可以在相同版本虛擬機中運行也可以在高版本虛擬機中運行(當然如果想使用高版本提供的新特性情況除外)。能明確類型的最好明確類型,不要完全依賴於java的自動類型轉換,比如第1個例子中把參數sbInput改成sbInput.toString()在兩個版本中就都可以正常運行。

 

附:查看class文件支持可以運行的jdk最低版本的方法,可以通過文本編輯器(如UtralEdit)打開class字節碼文件,察看第8個字節的值,版本對應關係如下:

第8位值(16進制)

10進制

對應jdk版本

2E

46

1.4.2

30

48

1.4

31

49

1.5

32

50

1.6

該值僅僅說明該class文件能夠被該版本或更高版本的虛擬機接受,但是能不能正常運行並不能得到保證(比如上面例子中的錯誤),如果使用javac編譯時未添加任何參數那麼該值說明class字節碼文件是由該版本的虛擬機編譯生成,當然能夠確在該版本中正常執行。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章