11

Please take a look at my code:

Object longL = 2548214;
Map<String, Object> map = new HashMap<String, Object>(1);
map.put("LongNumber", longL);
List<Map<String, Object>> returnlist = new ArrayList(10);
returnlist.add(map);

List<Object> versionMap1 = new ArrayList(10);
versionMap1.add(returnlist);

List<Map<String, String>> docIdVersionNameMap = new ArrayList<>();
docIdVersionNameMap.addAll((List<Map<String, String>>)versionMap1.get(0));

Map<String, String> versionDoc=docIdVersionNameMap.get(0);

Map<String,String> versionDocInfo=new HashMap<String,String>(1);
versionDocInfo.put(versionDoc.get("LongNumber"),"abc");
System.out.println(versionDocInfo.toString());

In Java_1.8_60 (Compile & Run) this code is running fine, but when compiled and run in Java 11 it is throwing the following exception:

Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of l
oader 'bootstrap')
        at teststringandlong.Trial.main(Trial.java:35)

Are there any changes in Java 11 regarding HashMap?

Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
RutujaG
  • 119
  • 1
  • 3
  • 7
    I'm getting the same error in Java 8, and it's not surprising - you are trying to put an `Integer` as a key in a `Map`. – Eran Mar 17 '19 at 07:59
  • 2
    Java `1.8_60` is pretty old but still hard to believe it is running correctly. With a bit newer version I got `java version "1.8.0_112" Java(TM) SE Runtime Environment (build 1.8.0_112-b15) ... Exception in thread "main" java.lang.ClassCastException: ...` – user85421 Mar 17 '19 at 08:56
  • 1
    @YassinHajaj as key: `versionDocInfo.put(versionDoc.get("LongNumber"),"abc");` – Eran Mar 17 '19 at 09:23
  • @Eran, you're right indeed. What's strange is that the same exact code works on `ideone.com [HotSpot 8u112]` and `JDoodle [JDK 10.0.1]`. https://ideone.com/296BMB & https://www.jdoodle.com/online-java-compiler (just copy past it with correct imports) – Yassin Hajaj Mar 17 '19 at 09:35
  • I don't get the close votes. Please don't close it, this code works on multiple platforms, and on my PC locally too. – Yassin Hajaj Mar 17 '19 at 10:13

1 Answers1

18

The ClassCastException being thrown is correct. Not having it thrown was caused by a bug in javac, which was fixed in JDK 9 by JDK-8058199. Your code is technically relying on heap pollution not being picked up, so it was never guaranteed to not break.

Basically, in Java 11 (but starting from 9), an extra cast is inserted after getting the value for "LongNumber" from the map on the second to last line. This:

versionDocInfo.put(versionDoc.get("LongNumber"),"abc");

Is compiled as:

versionDocInfo.put((String) versionDoc.get("LongNumber"),"abc");

When compiling your code with javac 1.8.0_162, the bytecode for the second to last line is:

 114: aload         7
 116: aload         6
 118: ldc           #6                  // String LongNumber
 120: invokeinterface #16,  2           // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
 125: ldc           #17                 // String abc
 127: invokeinterface #7,  3            // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

Notice that there is no checkcast instruction after 120:. However, when using javac 9.0.4:

 114: aload         7
 116: aload         6
 118: ldc           #6                  // String LongNumber
 120: invokeinterface #16,  2           // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
 125: checkcast     #17                 // class java/lang/String
 128: ldc           #18                 // String abc
 130: invokeinterface #7,  3            // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

Notice that there is a checkcast instruction at 125:.

This instruction makes the difference, as it basically does an extra type check after getting the value from the versionDoc map. Basically doing this:

versionDocInfo.put((String) versionDoc.get("LongNumber"),"abc");

In Java 11 (starting from 9).


As noted in the comments; the type of the value for "LongNumber" is Integer, which is inside a Map<String, String> due to the unchecked cast a few lines earlier:

docIdVersionNameMap.addAll((List<Map<String, String>>) versionMap1.get(0));

Where you indirectly cast a Map<String, Object> to a Map<String, String>, even though one of the values is an Integer. The difference is only that there's an extra cast to check the type after getting the value from the map.

Note that the missing checkcast was a bug in javac, so compiling with a different compiler, or different versions of javac could result in different behavior.

Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
  • How can we explain that it runs on Java 10 on jdoodle.com and also that some of us have the code failing on Java 8 and some not? – Yassin Hajaj Mar 17 '19 at 13:46
  • @YassinHajaj Well, it's really `javac` that's at fault, not `java`, so if you're using e.g. Eclipse to compile the class you might get different behavior. If you can, you should check the byte code with `javap -c ...` – Jorn Vernee Mar 17 '19 at 13:49
  • 2
    @YassinHajaj I'm not sure about jdoodle.com, using the same version of Java 10 I am getting the CCE being thrown. Must be something about their setup... Testing with e.g. `var x = 10` this does not compile on jdoodle.com, so I guess they're using `javac` from JDK 8 to compile, and then running with java 10. – Jorn Vernee Mar 17 '19 at 13:55
  • Alright thanks, checkin the bytecode locally, it's indeed not invoking the checkcast operation ! Thanks, this is very interesting :) – Yassin Hajaj Mar 17 '19 at 13:57