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字节码文件是由该版本的虚拟机编译生成,当然能够确在该版本中正常执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值