Неопределенная ссылка: .. 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 выдает эту ошибку с ошибкой подписи.


person Bilal Yasar    schedule 06.09.2014    source источник
comment
Думаю, это может быть связано с этим вопросом: stackoverflow.com/questions/24448723/.   -  person Michael Petch    schedule 07.09.2014
comment
Итак, как я могу избежать ошибки, есть ли решение?   -  person Bilal Yasar    schedule 07.09.2014
comment
Я не использую плагин animal-sniffer-plugin, так что на самом деле я не могу им помочь. Я нашел эту информацию, которая может иметь некоторое отношение: mojo .codehaus.org / animal-sniffer-maven-plugin / examples /.   -  person Michael Petch    schedule 07.09.2014


Ответы (2)


Другой ответ предлагает модификацию вашего кода (с использованием keys() вместо keySet()), чтобы вы могли скомпилировать исходный код на Java 8 и запустить на Java 7. Я думаю, что это шаг назад.

Вместо:

  • Если ваша цель - создать производственную сборку вашего программного обеспечения, которая будет работать на Java 6, 7 и 8, то лучше всего делать производственные сборки на JDK 6.

  • Если ваша цель состоит в том, чтобы создавать сборки для разработки на Java 8 (но пока оставайтесь обратно совместимыми на уровне исходного кода), то измените конфигурации плагинов maven для animal-sniffer, чтобы они игнорировали эти классы; см. http://mojo.codehaus.org/animal-sniffer-maven-plugin/examples/checking-signatures.html для объяснения.

    Тем не менее, существует риск, что перехватчик животных слишком многое проигнорирует; например он не скажет вам, используете ли вы новые методы Java 8 в ConcurrentHashMap. Вам нужно будет учесть такую ​​возможность ....

  • Если ваша цель - перейти на Java 8 (чтобы вы могли начать использовать новые функции Java 8 в своем коде), просто сделайте это. Ваш код не будет обратно совместим, но вы не сможете вечно поддерживать старые версии Java ...

(Эти предложения не являются взаимоисключающими, если вы рассматриваете общую картину.)

person Stephen C    schedule 06.09.2014
comment
Я хочу создать все версии, и я думаю, что лучший ответ - это ваш второй ответ. я проигнорировал это, спасибо. - person Bilal Yasar; 07.09.2014
comment
Какой ответ предполагает использование 8 и 7? Моя? - person Stuart Marks; 07.09.2014
comment
@StuartMarks - Я написал изменение кода, чтобы вы могли создавать на Java 8 и запускать на Java 7. Вы это предлагали? Я так не думаю! - person Stephen C; 07.09.2014
comment
Достаточно справедливо, другой ответ, который я вижу, предлагал изменить код, но не говорил конкретно о 7 против 8, поэтому я не был уверен. - person Stuart Marks; 07.09.2014
comment
+1 к построению на JDK 6, чтобы работать на 6, 7 и 8. - person Stuart Marks; 07.09.2014

Вы должны иметь возможность использовать 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 содержит какие-либо зависимости от API-интерфейсов JDK 8, этот не будет работать при запуске с использованием JDK 7; это приведет к выдаче NoSuchMethodError во время выполнения.

Обратите внимание, что эта ошибка возникнет не сразу; это произойдет только тогда, когда будет сделана попытка вызвать рассматриваемый метод. Это потому, что связывание в Java выполняется лениво. Таким образом, вы можете иметь ссылки на несуществующие методы, скрывающиеся в вашем коде в течение произвольного периода времени, и ошибки не возникнут, если путь выполнения не попытается вызвать такой метод. (Это одновременно и благословение, и проклятие.)

Не всегда легко определить зависимости от новых API JDK, взглянув на исходный код. В этом случае, если вы компилируете с использованием JDK 8, вызов keySet() будет компилироваться со ссылкой на метод, который возвращает ConcurrentHashMap.KeySetView, потому что этот метод находится в библиотеке классов JDK 8. Параметр -target 1.7 делает результирующий файл класса совместимым с JDK 7, а параметр -source 1.7 ограничивает уровень языка JDK 7 (что в данном случае не применяется). Но в результате получается ни рыба, ни мясо: формат файла класса будет работать с JDK 7, но он содержит ссылки на библиотеку классов JDK 8. Если вы попытаетесь запустить это на JDK 7, конечно, он не сможет найти новые вещи, представленные в 8, поэтому возникает ошибка.

Вы можете попытаться обойти это, изменив исходный код, чтобы избежать зависимости от новых API JDK. Итак, если ваш код такой:

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 7 с использованием JDK 8, сделайте следующее:

/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 больше не требуется иметь старый rt.jar JDK для целей сборки, поскольку javac включает таблицы общедоступных API для более ранних выпусков.

**

В более общем плане проблема такого рода обычно возникает, когда JDK вводит ковариантные переопределения в новом выпуске JDK. Это произошло в JDK 9 с различными Buffer подклассами. Например, ByteBuffer.limit(int). был переопределен и теперь возвращает ByteBuffer, тогда как в JDK 8 и ранее этот метод был унаследован от Buffer и возвращал Buffer. (В этой области было добавлено несколько других ковариантных переопределений.) Системы, скомпилированные на JDK 9 с использованием только -source 8 -target 8, столкнутся с точно такой же проблемой с NoSuchMethodError. Решение то же: используйте --release 8.

person Stuart Marks    schedule 06.09.2014
comment
поэтому, если я не использую ничего нового в этом классе из jdk 8, я могу использовать -target 1.7. Это правда? - person Bilal Yasar; 07.09.2014
comment
@bou Строго говоря, да, но с практической точки зрения невозможно избежать утечки фрагментов библиотеки классов JDK 8 в ваши построенные классы. Это хороший пример, поскольку тип возвращаемого значения keySet()'s изменился, чтобы вернуть конкретный класс Java 8, по сути, нет способа вызвать keySet() без включения зависимости Java 8. - person Stuart Marks; 07.09.2014
comment
мы также столкнулись с этой проблемой при сборке с JDK 8 с -source 1.6 -target 1.6, а затем при попытке запустить с JRE 6. Я удивлен, что API JDK 8 изменился для такого фундаментального метода, как keySet () в Java 8, чтобы вызвать двоичную несовместимость с более ранними версиями Java. Если бы подпись метода в Java 8 оставалась прежней: ‹code› Set ‹K› keySet () ‹/code›, тогда он, вероятно, был бы двоично совместим. Нашим обходным решением было использование entrySet (), так как у нас есть возможность удалить некоторые элементы, когда мы перебираем карту. - person Neon; 03.09.2015