java class的兼容问题
前不久在工作中,遇到了几次编译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的兼容问题的更多相关文章
- Java与.NET兼容的RSA密钥持久化方法
默认情况下,.NET生成的RSA密钥对可以用XML或字节流来保存,而JAVA中生成的RSA密钥对只能用字节流来保存.而它们的字节流格式不同,就导致Java中生成的RSA密钥对不能在.NET中使用,而. ...
- android开发 java与c# 兼容AES加密
由于android客户端采用的是AES加密,服务器用的是asp.net(c#),所以就造成了不一致的加密与解密问题,下面就贴出代码,已经试验过. using System; using System. ...
- Java读取excel(兼容03和07格式)
读取excel,首先需要下载POI的jar,可以去官网下,也可以在这里下载 一.简单说明 excel2003和excel2007区别比较大,最直观的感受就是扩展名不一样,哈哈 不过,使用POI的API ...
- 记录java版本不兼容的坑,(kafka运行报错)
启动kafka报错 错误原因是: 由较高版本的jdk编译的java class文件 试图在较低版本的jvm上运行的报错 解决办法是: 查看java版本 C:\Users\Administrator&g ...
- Java Controller下兼容xls和xlsx且可识别合并单元格的excel导入功能
1.工具类,读取单元格数据的时候,如果当前单元格是合并单元格,会自动读取合并单元格的值 package com.shjh.core.util; import java.io.IOException; ...
- eclipse环境问题-java版本不兼容
有时候虽然我们给项目配置的jdk版本.项目编译版本都一直,但是还是会报如下的错误: Description Resource Path Location Type Java compiler leve ...
- 使用 Java 开发兼容 IPv6 的网络应用程序
根据现有 IPv4 地址的部署速度,剩余的地址将在 10 到 20 年被使用殆尽.因此网络逐渐从 IPv4 向 IPv6 转换是不可避免的,相应的各种网络应用程序都将支持 IPv6.对于 Java,从 ...
- C++、Java、Objective-C、Swift 二进制兼容测试
鉴于目前动态库在iOS App中使用越来越广泛,二进制的兼容问题可能会成为一个令人头疼的问题.本文主要对比一下C++.Java.Objecive-C和Swift的二进制兼容问题. iOS端动态库使用情 ...
- Java基础知识笔记(三:文件与数据流)
一.输入流与输出流 输入流将数据从文件.标准输入或其他外部输入设备中加载到内存.输出流的作用则刚好相反,即将在内存中的数据保存到文件中,或传输给输出设备.输入流在Java语言中对应于抽象类java.i ...
随机推荐
- C#有关数组内存的释放及动态数组问题
一.数组内存释放问题 数组内存的释放可以按照如下语句实现: string [] aa=new string[2]; aa[0]="A"; aa[1]="B"; ...
- 1219 spring3 项目总结
Spring3 项目总结 列志华 (组长) http://www.cnblogs.com/liezhihua/ 团队guihub https://github.com/LWHTF/OrderingFo ...
- Linux常用性能调优工具索引
root@ubuntu:~# dstat----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--usr sys i ...
- rabbitmq 学习足迹
http://www.cnblogs.com/me-sa/archive/2012/10/20/RabbitMQ_VHost_Exchanges_queues_bindings_and_Channel ...
- redhat yum 从 iso 安装
背景: 1)yum 在没有注册的redhat中无法使用,不能去自动搜索redhat的库 2)使用者不能上网 方法摘自网络,就是下载ISO文件,yum的下载点指向ISO的mount后(也就是解压缩)的目 ...
- UEditor演变的迷你版编辑器
建立一个demo.html文件,首先在需要添加编辑器的地方加入以下代码,使用style可以设置编辑器的宽度和高度. <script type="text/plain" id= ...
- convert return char from sql server 2008 r2 or below version to c#
C# string.Replace((char)13, ' ') //newline char; string.Replace((char)10, ' ') //return char;
- ulimit
.修改ulimit vim /etc/security/limits.conf * soft nproc * hard nproc * soft nofile * hard nofile vim /e ...
- {POJ}{3925}{Minimal Ratio Tree}{最小生成树}
题意:给定完全无向图,求其中m个子节点,要求Sum(edge)/Sum(node)最小. 思路:由于N很小,枚举所有可能的子节点可能情况,然后求MST,memset()在POJ G++里面需要cstr ...
- winform开发之UI系列
1.如何构造一个漂亮的主窗体 主要讲述如何对一个新建窗体的美化过程,涉及到经常需要用到的几个属性我会着重强调它的用法,并不断更新它,因为楼主也正在探索中.... 步骤如下: vs新建一个winform ...