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

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 навсегда...

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

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

**

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

Другие вопросы по тегам