I've recently updated my Android project for Android Studio 3. I wanted to support Java 8 language features, so added the following to build.gradle:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
I then run my app on an Android 8.0.0 device. At runtime I see
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)
I gather this is to do with the fact that the signature of keySet()
was changed in Java 8 from returning Set<K>
to returning KeySetView<K,V>
.
The line that has caused the exception looks like this:
for (Long id : mSomeMap.keySet())
KeySetView
implements Set
, it's certainly Iterable
, so whether this line is interpreted as Java 7 or Java 8 I'd have thought it would work either way. My understanding of Java fundamentals is sketchy - what's going on here?
Update
My flaky understanding so far is this:
While Android now supports some Java 8 language features, its API is not identical to Java 8. In particular, Android's implementation of ConcurrentHashMap.keySet()
returns a Set
, while the Java 8 implementation of ConcurrentHashMap.keySet()
returns a KeySetView
.
Somehow, Android Studio has managed to compile my app with the standard Java 8 JDK and therefore at runtime expects to find a method with the signature KeySetView<K,V> keySet()
. However, Android's ConcurrentHashMap
does not have a method with this signature, so I get a NoSuchMethodError
.
I'm no closer to working out how or why Android Studio is building with an incompatible JDK. In Project Structure, 'Use embedded JDK (recommended)' is checked, so I assume Android Studio is building with the JDK that came bundled with it.
Not a solution
Most comments/answers so far have pointed out that I can just declare the ConcurrentHashMap
as a Map
to get around this. That is a workaround, and not a solution. If the underlying problem is that my app is being built with the wrong JDK, then there may be other instances where method signatures diverge, and since I can't live test 100% of code paths in what is a large project, I can't guarantee that more NoSuchMethodErrors
won't be thrown at run time.
Set the JDK version Open your project in Android Studio and select File > Settings... > Build, Execution, Deployment > Build Tools > Gradle (Android Studio > Preferences... > Build, Execution, Deployment > Build Tools > Gradle on a Mac). Under Gradle JDK, choose the Embedded JDK option. Click OK.
A copy of the latest OpenJDK comes bundled with Android Studio 2.2 and higher, and this is the JDK version we recommend you use for your Android projects.
Contrary to JetBrains' documentation, but according to Configure Android Studio, the configuration is currently under a "Google" folder: Windows: %APPDATA%\Google\<product><version> Mac: ~/Library/Application Support/Google/<product><version> Linux: ~/.
Java 8 source code that works in latest version of Android, can be made to work in older versions of Android.
I'm using this workaround and seems is working.
private final Map<ImageView, Request> mPendingRequests =
new ConcurrentHashMap<ImageView, Request>();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With