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

Ответы

Ответ 1

Другой ответ предлагает модификацию вашего кода (используя 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 для объяснения.

    Однако существует риск того, что животное-снифер будет слишком сильно игнорировать; например он не скажет вам, используете ли вы новые методы Java 8 в ConcurrentHashMap. Вам нужно будет разрешить эту возможность.

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

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

Ответ 2

Вы можете использовать chm.keySet() с ConcurrentHashMap.

Между Java 7 и Java 8 метод ConcurrentHashMap.keySet() изменился с возврата Set<K> на возврат ConcurrentHashMap.KeySetView<K,V>. Это ковариантное переопределение, поскольку KeySetView<K,V> реализует Set<K>. Он также совместим как с исходным, так и с двоичным кодом. То есть, тот же исходный код должен работать нормально при построении и запуске на 7, а когда он построен и запущен на 8. Бинарный файл, построенный на 7, должен также работать на 8.

Почему ссылка undefined? Я подозреваю, что есть проблема с конфигурацией сборки, которая смешивает биты с 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. Это эффективно устанавливает исходный, целевой и bootclasspath на один и тот же уровень платформы 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.