前不久在工作中,遇到了几次编译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_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #838 // Method keySet:()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;
4: areturn
LineNumberTable:
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

java class的兼容问题的更多相关文章

  1. Java与.NET兼容的RSA密钥持久化方法

    默认情况下,.NET生成的RSA密钥对可以用XML或字节流来保存,而JAVA中生成的RSA密钥对只能用字节流来保存.而它们的字节流格式不同,就导致Java中生成的RSA密钥对不能在.NET中使用,而. ...

  2. android开发 java与c# 兼容AES加密

    由于android客户端采用的是AES加密,服务器用的是asp.net(c#),所以就造成了不一致的加密与解密问题,下面就贴出代码,已经试验过. using System; using System. ...

  3. Java读取excel(兼容03和07格式)

    读取excel,首先需要下载POI的jar,可以去官网下,也可以在这里下载 一.简单说明 excel2003和excel2007区别比较大,最直观的感受就是扩展名不一样,哈哈 不过,使用POI的API ...

  4. 记录java版本不兼容的坑,(kafka运行报错)

    启动kafka报错 错误原因是: 由较高版本的jdk编译的java class文件 试图在较低版本的jvm上运行的报错 解决办法是: 查看java版本 C:\Users\Administrator&g ...

  5. Java Controller下兼容xls和xlsx且可识别合并单元格的excel导入功能

    1.工具类,读取单元格数据的时候,如果当前单元格是合并单元格,会自动读取合并单元格的值 package com.shjh.core.util; import java.io.IOException; ...

  6. eclipse环境问题-java版本不兼容

    有时候虽然我们给项目配置的jdk版本.项目编译版本都一直,但是还是会报如下的错误: Description Resource Path Location Type Java compiler leve ...

  7. 使用 Java 开发兼容 IPv6 的网络应用程序

    根据现有 IPv4 地址的部署速度,剩余的地址将在 10 到 20 年被使用殆尽.因此网络逐渐从 IPv4 向 IPv6 转换是不可避免的,相应的各种网络应用程序都将支持 IPv6.对于 Java,从 ...

  8. C++、Java、Objective-C、Swift 二进制兼容测试

    鉴于目前动态库在iOS App中使用越来越广泛,二进制的兼容问题可能会成为一个令人头疼的问题.本文主要对比一下C++.Java.Objecive-C和Swift的二进制兼容问题. iOS端动态库使用情 ...

  9. Java基础知识笔记(三:文件与数据流)

    一.输入流与输出流 输入流将数据从文件.标准输入或其他外部输入设备中加载到内存.输出流的作用则刚好相反,即将在内存中的数据保存到文件中,或传输给输出设备.输入流在Java语言中对应于抽象类java.i ...

随机推荐

  1. react在jsx语法中实现for循环

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script sr ...

  2. Android IOS WebRTC 音视频开发总结(八十)-- NUBOMEDIA: 首个WebRTC PaaS

    本文主要介绍NUBOMEDIA(我们翻译和整理的,译者:jiangpeng,校验:blacker),最早发表在[编风网] 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacker(微信ID:b ...

  3. python中x,y交换值的问题

    今天碰到了python和其他语言不同的问题:赋值语句 x,y,z=1,2,3,执行 z,x,y=y,z,x 后,x.y.z 中分别含有什么值? 我想的是 x=2  y=2  z=2 可调试后应该是:x ...

  4. ARM仿真器

    ARM仿真器需要将调试换成simulator,其他JLink设置不变.否则会提示无法装载.flash文件 在keil下开发的设置

  5. Xena测试仪的自动化

    Xena,Xena Networks公司的网络测试仪,也能覆盖以太网L2~L7层测试仪,但功能较简单,界面也很简洁,用起来比较直观方便. 1.Xena的自动化测试场景 测试PC上的AT框架--> ...

  6. 从append追加的<tr>里传ID参数给js函数

    今天这个小问题几乎把我整崩溃 $.each(data.list, function (index, item) { i++; shenhe = "待审核"; tixing = it ...

  7. 使用php技术实现无刷新的上传文件

  8. POJ(2187)用凸包求最远点对

    Beauty Contest http://poj.org/problem?id=2187 题目描述:输入n对整数点,求最距离远的点对,输出他们距离的平方和 算法:拿到这个题,最朴素的想法就是用2层循 ...

  9. 使用Axis2实现WebService的发布和调用

    一.Axis2的下载和安装 1.可从http://ws.apache.org/axis2/ 下载Axis2的最新版本:      可以下载如下三个zip包: axis2-1.7.3-bin.zip(用 ...

  10. 重走java--Step 3

    java基础(三)之枚举用法用法一:常量 public enum Color {    RED,GREEN,RED,YELLOW;}用法二:枚举中自定义方法/** * 枚举中自定义方法 */publi ...