Как-то создается приложение для Android с неправильным JDK (?)

Недавно я обновил свой проект Android для Android Studio 3. Я хотел поддерживать функции языка Java 8, поэтому добавил в build.gradle следующее:

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

Затем я запускаю свое приложение на устройстве Android 8.0.0. Во время выполнения я вижу

java.lang.NoSuchMethodError: No virtual method keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView; in class Ljava/util/concurrent/ConcurrentHashMap; or its super classes (declaration of 'java.util.concurrent.ConcurrentHashMap' appears in /system/framework/core-oj.jar)

Я понимаю, что это связано с тем фактом, что подпись keySet() была изменена в Java 8 с возврата Set<K> на возврат KeySetView<K,V>.

Строка, вызвавшая исключение, выглядит так:

for (Long id : mSomeMap.keySet())

KeySetView реализует Set, это определенно Iterable, так что интерпретируется ли эта строка как Java 7 или Java 8, я думаю, что она будет работать в любом случае. Мое понимание основ Java поверхностно - что здесь происходит?

Обновить

Мое шаткое понимание до сих пор таково:

Хотя Android теперь поддерживает некоторые функции языка Java 8, его API не идентичен Java 8. В частности, реализация ConcurrentHashMap.keySet() в Android возвращает Set, а реализация ConcurrentHashMap.keySet() в Java 8 возвращает KeySetView.

Каким-то образом Android Studio удалось скомпилировать мое приложение со стандартным JDK Java 8, и поэтому во время выполнения он ожидает найти метод с сигнатурой KeySetView<K,V> keySet(). Однако в Android ConcurrentHashMap нет метода с такой сигнатурой, поэтому я получаю NoSuchMethodError.

Я не приблизился к тому, чтобы понять, как и почему Android Studio использует несовместимый JDK. В структуре проекта установлен флажок «Использовать встроенный JDK (рекомендуется)», поэтому я предполагаю, что Android Studio строится с JDK, который поставляется вместе с ним.

Не решение

В большинстве комментариев/ответов до сих пор указывалось, что я могу просто объявить ConcurrentHashMap как Map, чтобы обойти это. Это обходной путь, а не решение. Если основная проблема заключается в том, что мое приложение создается с использованием неправильного JDK, то могут быть и другие случаи, когда сигнатуры методов расходятся, и, поскольку я не могу живой тест 100% путей кода в большом проекте, я не могу гарантировать, что больше NoSuchMethodErrors не будет выброшено во время выполнения.


person UtterlyConfused    schedule 23.08.2017    source источник
comment
Что такое core-oj.jar . Я вижу это в вашем сообщении об ошибке. Карта типа mSomeMap принадлежит пакету Java.util стандартной библиотеки Java или исходит из core-oj.jar?   -  person Geek    schedule 23.08.2017
comment
Я предположил, что эта банка была частью системы Android. Уверен, что это не часть моего проекта. mSomeMap это java.util.concurrent.ConcurrentHashMap, извините, что не указал это явно.   -  person UtterlyConfused    schedule 23.08.2017
comment
Попробуйте удалить core-oj.jar или перейдите в эту банку и разархивируйте ее. Посмотрите, содержит ли он пакет и класс java.util.concurrent.ConcurrentHashMap.   -  person Geek    schedule 23.08.2017
comment
Как я уже сказал, core-oj.jar не является банкой в ​​моем проекте. Я думаю, что он где-то зарыт в системных файлах Android и содержит основные классы Java.   -  person UtterlyConfused    schedule 23.08.2017
comment
Метод keySet в CHM Android не возвращает KeySetView, см. android.googlesource.com/platform/libcore/+/master/ojluni/src/ Откуда взялась строка for (Long id : mSomeMap.keySet()) - это ваш код или это часть скомпилированной библиотеки, которую вы используете?   -  person Stefan Zobel    schedule 23.08.2017
comment
Однажды у меня была похожая проблема (в Google App Engine, а не в Android) с файлом Jar, который был скомпилирован на Java 8 и переписан на Java 6 с использованием retrolambda. Несмотря на то, что исходный код этой библиотеки не содержал никаких ссылок на KeySetView, на этот тип ссылались в файле класса, который использовал класс CHM. В моем случае решением было изменить тип static для mSomeMap в этой библиотеке с CHM на Map.   -  person Stefan Zobel    schedule 23.08.2017
comment
@StefanZobel Это мой код. Итак, мой вопрос: если Android CHM не возвращает KeySetView, а мой код никогда явно не упоминает KeySetView, то откуда вообще взялось ожидание, что keySet() вернет KeySetView?   -  person UtterlyConfused    schedule 23.08.2017
comment
@UtterlyConfused Очень хороший вопрос. Можете ли вы опубликовать минимальный пример кода, чтобы я мог попытаться воспроизвести его?   -  person Stefan Zobel    schedule 23.08.2017
comment
Я могу легко воспроизвести это, но только когда я компилирую с помощью JDK 8 и запускаю на Android 8 (включая созданный javac файл класса в виде jar ). Я также вижу ссылку на keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView в сгенерированном javac байтовом коде. Итак, я подозреваю, что вам каким-то образом удается скомпилировать Oracle JRE?   -  person Stefan Zobel    schedule 23.08.2017
comment
Изменение типа static для mSomeMap с CHM на ConcurrentMap помогает. Но опять же, я считаю, что ваша проблема в том, что вы компилируете Java 8 rt.jar.   -  person Stefan Zobel    schedule 23.08.2017
comment
Что должно компилироваться, где это взять и как настроить Android Studio для компиляции?   -  person UtterlyConfused    schedule 23.08.2017
comment
Извините, я понятия не имею, как вы настроили свой проект Android Studio. Но что-то здесь не так. Этого не происходит в проекте Vanilla AS 3.0 beta 2, который должен использовать core-oj.jar автоматически. Возможно, будет лучше создать свой проект с нуля.   -  person Stefan Zobel    schedule 23.08.2017
comment
Итак, из интереса, как вы собрали APK с JDK 8, чтобы воспроизвести мою проблему?   -  person UtterlyConfused    schedule 23.08.2017
comment
Я скомпилировал простой класс, содержащий тестовый код с JDK 8, поместил его в банку, скопировал в каталог app/libs и добавил как зависимость проекта. Если я использую тот же исходный код в AS 3.0, все работает, как и ожидалось.   -  person Stefan Zobel    schedule 23.08.2017
comment
@StefanZobel есть идеи, как я могу продвинуться в этом? Как узнать, компилирую ли я rt.jar или core-oj.jar?   -  person UtterlyConfused    schedule 28.11.2017
comment
Что-то в настройке вашего проекта AS ужасно сломано. Как я уже сказал, простой обходной путь — изменить статический тип mSomeMap с ConcurrentHashMap на ConcurrentMap.   -  person Stefan Zobel    schedule 28.11.2017
comment
Это было бы удовлетворительно, если бы единственной разницей между Android и любым загадочным JDK, который я создаю, была подпись ConcurrentHashMap.keySet(). Интересно отметить, что при сборке с помощью gradle из командной строки (и вообще без участия Android Studio) я все еще сталкиваюсь с этой проблемой.   -  person UtterlyConfused    schedule 28.11.2017


Ответы (4)


Я использую этот обходной путь и, кажется, работает.

  private final Map<ImageView, Request> mPendingRequests =
            new ConcurrentHashMap<ImageView, Request>();
person Javier    schedule 10.09.2017
comment
Поскольку NoSuchMethodError генерируется во время выполнения, код, исправляющий его в тех местах, с которыми я столкнулся, к сожалению, не является решением. - person UtterlyConfused; 11.09.2017

просто приведите его к Map<?, ?>:

for (Long id : ((Map< Long, ?>)mSomeMap).keySet())
person user6648882    schedule 29.11.2017

Вы используете специальную реализацию ConcurrentHashMap

объявление 'java.util.concurrent.ConcurrentHashMap' появляется в /system/framework/core-oj.jar

у которого нет метода keySet()

person Thomas.L    schedule 23.08.2017
comment
Я интерпретировал ошибку как сообщение о том, что в core-oj.jar не было метода keySet() с сигнатурой KeySetView<K,V> keySet(), а не вообще не было никакого метода с таким именем. Очень туманно похоже, что класс в моем приложении ожидает найти keySet() со своей подписью Java 8, но не может его найти. Является ли реализация Java для Android лишь немного похожей на Java 8? И если да, то почему Android Studio построила это, не предупредив меня, что оно не будет работать? - person UtterlyConfused; 23.08.2017
comment
@UtterlyConfused Я думаю, что ваша интерпретация ожидает найти keySet () с его подписью Java 8, полностью верна. - person Anlon Burke; 23.08.2017
comment
проблема в том, что они имеют одинаковые имена пакетов и классов, когда вы компилируете, один из классов переопределяет другой, в то время как ваша IDE думает, что вы используете класс, который переопределяется - person Thomas.L; 23.08.2017

вы можете объявить ConcurrentMap интерфейс:

ConcurrentMap mSomeMap = new ConcurrentHashMap();

затем используйте так:

for (Long id : mSomeMap.keySet())

person yuxingxin    schedule 03.11.2017