ConcurrentHashMap崩溃使用JDK 8编译但是以JRE 7为目标的应用程序

我今天遇到了一个非常意想不到的错误,虽然我能够找到一种方法来解决整个问题,但我不确定我是否完全理解它为什么会这样做。

我正在使用的代码最初是用JDK 7环境编写的,当然是针对JRE 7.在代码中我使用的是ConcurrentHashMap ,需要迭代映射中的键。 为此,我使用了map.keySet() ,根据JavaDocs,它应返回Set 。 这工作正常,直到我们的构建环境切换到JDK8。

当我们转移到JDK8时,我确保在调用javac时调用1.7的目标/源。 当代码在想要遍历地图的键时开始失败时,我感到非常惊讶。 没有抛出任何错误,没有exception,线程只是停止了。 在做了一些研究后,我发现Java8的ConcurrentHashMap实现.keySet()方法返回一个KeySetView

我通过使用map.keySet()切换到使用map.keys()获取Enumerationmap.keys()

现在我对这个问题的猜测是,虽然项目是针对Java7编译的,因为使用了JDK8,Java8库被包含在内,但为什么在它出现不匹配时它没有抛出错误或exception?

正如这里所要求的是一段代码片段:

 class MapProcessing { private ConcurrentHashMap map = new ConcurrentHashMap(); public MapProcessing() { map.put("First",new Object()); map.put("Second",new Object()); map.put("Third",new Object()); } public void processing() { // when calling this type of loop causes a freeze on our system. for(String key : map.keySet()) { System.out.println(key); } } public void working() { // This is what I had to do to fix the problem. Enumeration keys = map.keys(); while(keys.hasMoreElements()) { String key = keys.nextElement(); System.out.println(key); } } } 

我们正在使用Oracle JDK 8 build 40在Windows 2012服务器上的javac中使用1.7和1.7的目标进行编译。

代码使用在Windows 2012服务器上运行的Oracle JVM 7 build 25运行。

如果我使用Java 8和javac -source 1.7 -target 1.8编译代码然后使用Java 7运行它我得到一个

线程“main”java.lang.NoSuchMethodError中的exception:
   java.util.concurrent.ConcurrentHashMap.keySet()Ljava / util的/并行/ ConcurrentHashMap的$ KeySetView;
     at stackoverflowt.Test.processing(Test.java:20)
     at stackoverflowt.Test.main(Test.java:27)   

这是因为字节代码看起来像

 public void processing();
    码:
        0:aload_0       
        1:getfield#4 //字段映射:Ljava / util / concurrent / ConcurrentHashMap;
        4:invokevirtual#10 //方法java / util / concurrent / ConcurrentHashMap.keySet :()Ljava / util / concurrent / ConcurrentHashMap $ KeySetView;
        7:invokevirtual#11 //方法java / util / concurrent / ConcurrentHashMap $ KeySetView.iterator :()Ljava / util / Iterator;
       10:astore_1      

并明确地引用了Java 7中不存在的ConcurrentHashMap $ KeySetView。我在Mac上使用Java 1.7.0_79和1.8.0_45

如果将代码更改为(仅使用Map接口):

 private Map map = new ConcurrentHashMap(); 

然后它对我有用。 Bytecode然后看起来像

 public void processing();
    码:
        0:aload_0       
        1:getfield#4 //字段映射:Ljava / util / Map;
        4:invokeinterface#10,1 // InterfaceMethod java / util / Map.keySet :()Ljava / util / Set;
        9:invokeinterface#11,1 // InterfaceMethod java / util / Set.iterator :()Ljava / util / Iterator;
       14:astore_1      

每当使用针对旧版本的-source参数使用较新的JDK构建项目时,您将收到此编译器警告:

warning: [options] bootstrap class path not set in conjunction with -source 1.7

这篇博客文章讨论了它的含义。

基本上,你得到这个警告是因为Java使用较旧的语言规则编译它,但是针对较新的类库…并且由于Oracle移动了一些内部类,因此Java 8版本存在一些兼容性问题。

修复是在编译时使用-bootclasspath参数将其指向旧版本的rt.jar