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

2022-09-03 18:37:12

我有一个项目,我正在用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,动物嗅探器插件给出了这个错误,带有签名错误。


答案 1

您应该能够与 一起使用。chm.keySet()ConcurrentHashMap

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

为什么是未定义的引用?我怀疑存在一个构建配置问题,它混合了Java 7和Java 8中的位。通常发生这种情况是因为在尝试为以前的版本进行编译时,会指定 and 选项,但未指定该选项。例如,请考虑以下事项:-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 进行编译,则 对 的调用将在对返回 a 的方法的引用中编译,因为这是在 JDK 8 类库中找到的方法。该选项使生成的类文件与 JDK 7 兼容,并且该选项将语言级别限制为 JDK 7(在这种情况下不适用)。但结果实际上既不是鱼也不是禽:类文件格式将与JDK 7一起使用,但它包含对JDK 8类库的引用。如果您尝试在JDK 7上运行它,当然它找不到8中引入的新内容,因此会发生错误。keySet()ConcurrentHashMap.KeySetView-target 1.7-source 1.7

您可以尝试通过修改源代码来解决此问题,以避免依赖于较新的 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

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

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

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

使用该选项时,不再需要使用较旧的JDK进行构建,因为包括早期版本的公共API表。--releasert.jarjavac

**

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


答案 2

另一个答案建议对代码进行修改(使用而不是 ),以便您可以在Java 8上编译源代码并在Java 7上运行。我认为这是倒退的一步。keys()keySet()

相反:

  • 如果您的目标是创建将在 Java 6、7 和 8 上运行的软件的生产构建,那么您最好的选择是在 JDK 6 上进行生产构建。

  • 如果您的目标是在Java 8上进行开发构建(但现在在源代码级别保持向后兼容),那么请更改动物嗅探器的maven插件配置以忽略这些类;有关说明,请参阅 http://mojo.codehaus.org/animal-sniffer-maven-plugin/examples/checking-signatures.html

    但是,存在动物嗅探器会忽略太多的风险;例如,它不会告诉您是否在 中使用新的Java 8方法。您需要考虑这种可能性....ConcurrentHashMap

  • 如果您的目标是迁移到 Java 8(以便您可以开始在代码中使用新的 Java 8 功能),那么只需执行此操作即可。您的代码不会向后兼容,但您永远无法支持旧版本的Java...

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


推荐