The strange thing however, is that the Map contains another (wrong) value, even though the Map value is also set using the same static final constant NOFLASH_IMAGE_URL. You can see that in the lower middle pane of the screen shot. So for some strange reason, the constant is evaluating to both the correct and the wrong value. I guess we could call it semi-constant in this particular case.
Anytime you see something weird like this, your first thought should be class loading problem. Based on my experience, the weirdest problems are often rooted in class loading. However this problem is of a different kind. For a better understanding, you have to know that the Class which initializes the Map is a dependency that ours project pulls in from a Maven dependency to library A. This library A then depends on another (transient) Maven library B which contains the NOFLASH_IMAGE_URL constant. Our project also defines a direct Maven dependency to library B. This is needed because library B changes quite often and we always want the latest version of B in our project. The latest version of B does contain the correct value the No-Flash image in the NOFLASH_IMAGE_URL constant. So one might think, that the Maven dependency resolution mechanism pulled in an old version of library B, but this wasn't the case.
A colleague then hinted me in the right direction. The Java compiler often does something called inlining for access to a static final variable. This is an optimization as the value of a final variable cannot change after it has been assigned. To verify this we ran the Java class file disassembler (javap) over the class file in library A.
78: pop 79: aload_2 80: ldc #108; //String noflash_image_url 82: ldc #109; //String http://static.playfish.com/shared/noflash.jpg 84: invokeinterface #100, 3; //InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
As you can see, the compiler is really inlining the value in places where we, by running a debugger over the source code, expect an evaluation of the static final variable at runtime. This is definitely something you have to be aware of. We can fix this, by re-compiling library A against a newer version of library B, which will inline the correct No-Flash image. Another option to prevent inlining would be to declare the constant like this:
public static final String NOFLASH_IMAGE_URL = new String ("...");
But then this might be really hard to understand for other developers and they might revert this change to a String literal if not properly documented. Update: I just realized that this problem is featured as Puzzle 93: Class Warfare in the Java Puzzlers book from Joshua Bloch. The compiler will inline all constant variables, such as Primitives and Strings which are initialized with a constant expression. Surprisingly, null is not inlined, neither are Java 5 enums.
0 Kommentare:
Kommentar veröffentlichen