18

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.

UtterlyConfused
  • 983
  • 1
  • 10
  • 18
  • What is core-oj.jar . I see this in your error message . Does mSomeMap type Map belongs to Java.util package of standard java library or is it coming from core-oj.jar ? – Geek Aug 23 '17 at 16:26
  • I had assumed that that jar was part of the Android system. Pretty sure it's not part of my project. `mSomeMap` is a `java.util.concurrent.ConcurrentHashMap`, sorry for not making that explicit. – UtterlyConfused Aug 23 '17 at 16:32
  • Try removing core-oj.jar or go to that jar amd unzip it. See if it contains the pacakage and the class java.util.concurrent.ConcurrentHashMap – Geek Aug 23 '17 at 16:45
  • As I said, core-oj.jar is not a jar in my project. My best guess is that it is buried in the Android system files somewhere and contains core Java classes. – UtterlyConfused Aug 23 '17 at 16:51
  • 4
    The `keySet` method in Android's CHM doesn't return a `KeySetView`, see https://android.googlesource.com/platform/libcore/+/master/ojluni/src/main/java/java/util/concurrent/ConcurrentHashMap.java?autodive=0%2F%2F%2F%2F%2F%2F#1245 Where does the line `for (Long id : mSomeMap.keySet())` come from - is it your code or it is part of a compiled library you use? – Stefan Zobel Aug 23 '17 at 16:57
  • 2
    I once had a similar problem (on Google App Engine, not Android) with a Jar file that has been compiled on Java 8 and rewritten to Java 6 using `retrolambda`. Even though the source code of that library didn't contain any references to `KeySetView` that type was referenced in the class file that was using the `CHM`. The solution, in my case, was to change the `static` type of `mSomeMap` in that library from `CHM` to `Map`. – Stefan Zobel Aug 23 '17 at 17:08
  • @StefanZobel It is my code. So my question is, if Android's CHM doesn't return `KeySetView` and my code never explicitly mentions `KeySetView`, then where did the expectation that `keySet()` would return a `KeySetView` even come from in the first place? – UtterlyConfused Aug 23 '17 at 17:41
  • @UtterlyConfused Very good question. Can you post a minimal code example so that I can try to reproduce it? – Stefan Zobel Aug 23 '17 at 17:45
  • 4
    I can reproduce this easily, but **only** when I **compile** with JDK 8 and **run** on Android 8 (including the javac produced class file as a jar). I also see the reference to `keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView` in the javac generated byte code. So, I suspect you are somehow managing to compile against an Oracle JRE? – Stefan Zobel Aug 23 '17 at 19:43
  • 4
    Changing the `static` type of `mSomeMap` from `CHM` to `ConcurrentMap` helps. But again, I believe your problem is that you are compiling against the Java 8 `rt.jar`. – Stefan Zobel Aug 23 '17 at 19:52
  • What *should* I be compiling against, where do I get it, and how do I configure Android Studio to compile with it? – UtterlyConfused Aug 23 '17 at 20:00
  • 1
    Sorry, I have no clue how you've configured your Android Studio project. But something is wrong here. This doesn't happen in a vanilla AS 3.0 beta 2 project which should use the `core-oj.jar` automatically. Perhaps, it would be best to set up your project from scratch. – Stefan Zobel Aug 23 '17 at 20:03
  • So out of interest, how did you build an APK compiling with JDK 8 to repro my issue? – UtterlyConfused Aug 23 '17 at 20:07
  • 1
    I've compiled a simple class containing the test code with JDK 8, put that into a jar, copied it to the app/libs directory and added it as a project dependency. If I use the same source code in AS 3.0 everything runs as expected. – Stefan Zobel Aug 23 '17 at 20:10
  • @StefanZobel any idea how I can progress on this? How can I tell if I'm compiling against rt.jar or core-oj.jar? – UtterlyConfused Nov 28 '17 at 16:07
  • Something in the setup of your AS project is horribly broken. As I said, a simple workaround is to the change the static type of `mSomeMap` from `ConcurrentHashMap` to `ConcurrentMap`. – Stefan Zobel Nov 28 '17 at 16:28
  • That would be satisfactory if the only single difference between Android and whatever mystery JDK I'm building with was the signature of ConcurrentHashMap.keySet(). It is interesting to note that building with gradle from the comand line (and not involving Android Studio at all) I still run into this problem. – UtterlyConfused Nov 28 '17 at 16:39

4 Answers4

1

I'm using this workaround and seems is working.

  private final Map<ImageView, Request> mPendingRequests =
            new ConcurrentHashMap<ImageView, Request>();
OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
Javier
  • 1,469
  • 2
  • 20
  • 38
  • Since NoSuchMethodError is thrown at runtime, a code fix it the places I happen to have encountered is not the solution unfortunately. – UtterlyConfused Sep 11 '17 at 09:32
1

just cast it to Map<?, ?>:

for (Long id : ((Map< Long, ?>)mSomeMap).keySet())
g00glen00b
  • 41,995
  • 13
  • 95
  • 133
0

You are using a special implementation of ConcurrentHashMap

declaration of 'java.util.concurrent.ConcurrentHashMap' appears in /system/framework/core-oj.jar

which does not have method keySet()

Thomas.L
  • 321
  • 1
  • 6
  • 2
    I interpreted the error as saying that core-oj.jar did not have a method `keySet()` with the signature `KeySetView keySet()`, rather than not having any method by that name at all. *In very woolly terms* it looks like the class in my app is expecting to find `keySet()` with its Java 8 signature, but can't find it. Is the Android implementation of Java only a bit Java 8-like? And if so, how come Android Studio has built this without warning me that it's not going to run? – UtterlyConfused Aug 23 '17 at 16:45
  • 1
    @UtterlyConfused I think your interpretation of "expects to find a keySet() with its Java 8 signature" is entirely correct. – Anlon Burke Aug 23 '17 at 17:14
  • 1
    the problem is that they have the same package & class names, when you compile, one of the class is overriding the other while your IDE is thinking you are using the class which gets overriden – Thomas.L Aug 23 '17 at 17:20
0

you can declare ConcurrentMap interface:

ConcurrentMap mSomeMap = new ConcurrentHashMap();

then use like this:

for (Long id : mSomeMap.keySet())

yuxingxin
  • 1
  • 4