“未定义的引用:.. ConcurrentHashMap.keySet()”在Java 8中构建时

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了“未定义的引用:.. ConcurrentHashMap.keySet()”在Java 8中构建时相关的知识,希望对你有一定的参考价值。

我有一个项目,我用jdk 6,7,8构建这个项目,我的目标是1.6

当我构建jdk 8时,我收到此错误:

Undefined reference: java.util.concurrent.ConcurrentHashMap.KeySetView java.util.concurrent.ConcurrentHashMap.keySet()

因为我在这行中有这个代码:

   final Iterator<CLASS_NAME> itr = hashMap.keySet().iterator();

如何避免错误,我在互联网上进行了一些搜索,并且由于java 8改变了它的返回类型键集,我得到了错误。这是任何解决方案。我正在使用maven,而animal-sniffer-plugin会出现此错误,并出现签名错误。

答案

另一个答案建议修改你的代码(使用keys()而不是keySet()),这样你就可以在Java 8上编译你的源代码并在Java 7上运行。我认为这是一个逆行的步骤。

代替:

  • 如果您的目标是创建将在Java 6,7和8上运行的软件的生产版本,那么您最好的选择是在JDK 6上进行生产。
  • 如果您的目标是在Java 8上进行开发构建(但目前在源代码级别保持向后兼容),那么更改动物嗅探器的maven插件配置以忽略这些类;请参阅http://mojo.codehaus.org/animal-sniffer-maven-plugin/examples/checking-signatures.html以获得解释。 然而,动物嗅探器有可能忽视太多;例如如果你在ConcurrentHashMap中使用新的Java 8方法,它不会告诉你。你需要考虑到这种可能性......
  • 如果您的目标是转向Java 8(以便您可以开始在代码中使用新的Java 8功能),那么就这样做吧。您的代码不会向后兼容,但您永远不能支持旧版本的Java ...

(如果考虑大局,这些建议并不相互排斥。)

另一答案

你应该能够使用chm.keySet()ConcurrentHashMap

在Java 7和Java 8之间,ConcurrentHashMap.keySet()方法确实从返回Set<K>变为返回ConcurrentHashMap.KeySetView<K,V>。这是一个协变覆盖,因为KeySetView<K,V>实现Set<K>。它也是源和二进制兼容的。也就是说,相同的源代码在7上构建和运行时以及在8上构建和运行时应该可以正常工作。构建在7上的二进制文件也应该在8上运行。

为什么未定义参考?我怀疑有一个构建配置问题混合来自Java 7和Java 8的位。通常会发生这种情况,因为在尝试编译先前版本时,指定了-source-target选项,但未指定-bootclasspath选项。例如,请考虑以下事项:

/path/to/jdk8/bin/javac -source 1.7 -target 1.7 MyClass.java

如果MyClass.java包含对JDK 8 API的任何依赖性,那么在使用JDK 7运行时这将不起作用;它会导致在运行时抛出NoSuchMethodError

请注意,此错误不会立即发生;只有在尝试调用相关方法时才会发生。这是因为Java中的链接是懒惰的。因此,您可以在代码中随机引入不存在的方法一段任意时间,并且除非执行路径尝试调用此类方法,否则不会发生错误。 (这既是一种祝福,也是一种诅咒。)

通过查看源代码来识别对新JDK API的依赖并不总是容易的。在这种情况下,如果使用JDK 8进行编译,对keySet()的调用将在对返回ConcurrentHashMap.KeySetView的方法的引用中进行编译,因为这是在JDK 8类库中找到的方法。 -target 1.7选项使得生成的类文件与JDK 7兼容,而-source 1.7选项将语言级别限制为JDK 7(在这种情况下不适用)。但结果实际上既不是鱼也不是鸡:类文件格式适用于JDK 7,但它包含对JDK 8类库的引用。如果你试图在JDK 7上运行它,当然它找不到8中引入的新东西,所以发生错误。

您可以尝试通过修改源代码来解决此问题,以避免依赖于较新的JDK API。所以,如果您的代码是这样的:

ConcurrentHashMap<K, V> hashMap = ... ;
final Iterator<CLASS_NAME> itr = hashMap.keySet().iterator();

您可以将其更改为:

ConcurrentHashMap<K, V> hashMap = ... ;
final Iterator<CLASS_NAME> itr = ((Map<K, V>)hashMap).keySet().iterator();

这可以使用,但我不推荐它。首先,它会破坏你的代码。其次,在这样的变化需要应用的地方并不明显,因此很难判断你何时收到所有案例。第三,它很难维持,因为这个演员阵容的原因并不明显,很容易被重构。

正确的解决方案是确保您的构建环境完全基于您要支持的最旧的JDK版本。然后,二进制文件应在以后的JDK上保持不变。例如,要使用JDK 8编译JDK 7,请执行以下操作:

/path/to/jdk8/bin/javac -source 1.7 -target 1.7 -bootclasspath /path/to/jdk7/jre/lib/rt.jar MyClass.java

除了指定-source-target选项之外,指定-bootclasspath选项还将限制对在该位置找到的API的所有依赖关系,当然这必须与为其他选项指定的版本匹配。这将防止任何对JDK 8的无意依赖性蔓延到生成的二进制文件中。

在JDK 9及更高版本中,添加了一个新选项--release。这有效地将源,目标和引导类路径一次性设置为相同的JDK平台级别。例如:

/path/to/jdk9/bin/javac --release 7 MyClass.java

当使用--release选项时,不再需要将旧的JDK的rt.jar用于构建目的,因为javac包含早期版本的公共API表。

**

更一般地说,当JDK在新的JDK版本中引入协变覆盖时,往往会出现这种问题。这发生在JDK 9中,包含各种Buffer子类。例如,ByteBuffer.limit(int)被覆盖并且现在返回ByteBuffer,而在JDK 8和更早版本中,此方法继承自Buffer并返回Buffer。 (在这个领域增加了几个其他的协变覆盖。)仅使用-source 8 -target 8在JDK 9上编译的系统将与NoSuchMethodError完全相同。解决方案是相同的:使用--release 8

以上是关于“未定义的引用:.. ConcurrentHashMap.keySet()”在Java 8中构建时的主要内容,如果未能解决你的问题,请参考以下文章