簡體   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