您的位置:首页 > 编程语言 > Java开发

JDK版本不同导致的运行时错误

2013-12-06 21:10 225 查看
JDK版本不同导致的运行时错误
最近有一同事编写的java程序在本地开发环境中能够正常运行,但是复制到实际环境中运行时报错(开发环境操作系统windows,程序实际运行环境linux),异常信息如下:

java.lang.NoSuchMethodError:java.lang.StringBuffer:methodinsert(ILjava/lang/CharSequence;)Ljava/lang/StringBuffer;notfound

atFirstApp.caozuoqueren$5.actionPerformed(caozuoqueren.java:393)

atjavax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1815)

atjavax.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(intdstOffset,

CharSequences)


StringBuffer
insert(intdstOffset,

CharSequences,intstart,intend)




代码中调用insert方法时第2个参数为sbInput,类型为StringBuffer,但是1.4和1.5版本中都没有定义第2个参数类型为StringBuffer的insert方法,程序是如何编译通过的哪?别忘了java支持对象类型之间的自动转换,在1.5版本中StringBuffer的声明如下:

publicfinalclassStringBuffer

extendsObject

implementsSerializable,CharSequence


可以看到StringBuffer类实现了CharSequence接口,因此一个StringBuffer对象其实也可以当作一个CharSequence对象来看待(java的多态性),在使用jdk1.5版本编译程序时由于该版本中StringBuffer类中存在方法
insert
(intdstOffset,

CharSequences)
的定义,因此javac编译程序会把参数sbInput的类型由StringBuffer自动转换成
CharSequence,这在1.5版本中运行时是没问题的,但是移植到1.4版本中运行时由于StringBuffer类没有定义参数为CharSequence类型的insert方法,因此会报本文开头出给出的异常。

类似的BigDecimal类也存在类似问题,编写一测试程序如下:

importjava.math.*;

publicclassTestBigDecimal{

publicstaticvoidmain(String[]args){

try{

BigDecimalbd1=newBigDecimal("1");

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

}catch(Exceptione){

System.out.println(e);

}

try{

BigDecimalbd2=newBigDecimal(2);

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

}catch(Exceptione){

System.out.println(e);

}

}

}

A.使用jdk1.5进行编译,javacTestBigDecimal.java

然后在1.4版本下运行会抛出一下异常:

Exceptioninthread"main"java.lang.UnsupportedClassVersionError:TestBigDecimal(Unsupportedmajor.minorversion49.0)

。。。

B.使用jdk1.5进行编译,添加source参数,javac–source1.4TestBigDecimal.java

然后在1.4版本下运行会抛出以下异常:

bd1,BigDecimal("1")=1

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

atTestBigDecimal.main(TestBigDecimal.java:13)

。。。

C.使用jdk1.5进行编译,添加source和target参数,javac–source1.4–target1.4TestBigDecimal.java

然后在1.4版本下运行会抛出以下异常:

bd1,BigDecimal("1")=1

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

atTestBigDecimal.main(TestBigDecimal.java:13)

。。。

分析上面的各种情况产生的结果,在A这种情况下由于是在1.5版本中编译成的class文件,放到1.4版本下运行时不受支持,即1.4版本不认可1.5版本生成的class文件类结构(很容易理解,软件版本的向下兼容性)。在B这种情况下,通过添加参数source对于生成的class文件提供与1.4版本的源兼容性,可以看到字节码文件可以在1.4下运行,第1个输出语句成功执行,第2个再构造BigDecimalbd2=newBigDecimal(2);对象时抛出异常。在C这种情况下除了添加source参数外还添加了target参数用于生成特定
VM版本的类文件,但是这种情况下和B报错一样(这地方有些疑惑,按照javac中对target参数的说明,编译时就应该和使用1.4编译器的效果是完全一样的,但从结果看并非如此)。产生这种错误的原因是两个版本中BigDecimal类构造器的差异导致,BigDecimal类在1.4版本中构造函数其中的两个:BigDecimal(doubleval)和

BigDecimal(Stringval),在1.5版本当中除了上述的两个之外又新增加了BigDecimal(intval),程序中BigDecimal
bd2=newBigDecimal(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-source1.4TestBigDecimal.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字节码文件是由该版本的虚拟机编译生成,当然能够确在该版本中正常执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐