繁体   English   中英

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

[英]“Undefined reference: .. ConcurrentHashMap.keySet()” when building in 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 给出了这个错误,签名错误。

您应该能够将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,当然这些 API 必须与为其他选项指定的版本相匹配。 这将防止对 JDK 8 的任何无意依赖蔓延到生成的二进制文件中。

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

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

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

**

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

另一个答案建议修改您的代码(使用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...

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM