59

The Java static compiler (javac) inlines some static final variables and brings the values directly to the constant pool. Consider the following example. Class A defines some constants (public static final variables):

public class A {
    public static final int INT_VALUE = 1000;
    public static final String STRING_VALUE = "foo";
}

Class B uses these constants:

public class B {
    public static void main(String[] args) {
        int i = A.INT_VALUE;
        System.out.println(i);
        String s = A.STRING_VALUE;
        System.out.println(s);
    }
}

When you compile class B, javac gets the values of these constants from class A and inlines these values in B.class. As a result, the dependency B had to class A at the compile time is erased from the bytecode. This is a rather peculiar behavior because you are baking in the values of these constants at the time of compilation. And you would think that this is one of the easiest things that the JIT compiler can do at runtime.

Is there any way or any hidden compiler option that lets you disable this inlining behavior of javac? For the background, we're looking into doing bytecode analysis for dependency purposes, and it is one of the few cases where bytecode analysis fails to detect compile-time dependencies. Thanks!

Edit: this is a vexing issue because normally we don't control all the source (e.g. third-party libraries that define constants). We're interested in detecting these dependencies from the perspective of using the constants. Since the reference is erased from the code that uses the constants, there is no easy way to detect them, short of doing source code analysis.

Lyubomyr Shaydariv
  • 20,327
  • 12
  • 64
  • 105
sjlee
  • 7,726
  • 2
  • 29
  • 37
  • 1
    Good Question!!! Sometime this inlining of constants cause weird issues... But I haven't seen a proper way to restrict it. – Ankit Bansal Aug 19 '10 at 17:11

9 Answers9

47

Item 93 of Java Puzzlers (Joshua Bloch) says that you can work round this by preventing the final value from being considered a constant. For example:

public class A {
  public static final int INT_VALUE = Integer.valueOf(1000).intValue();
  public static final String STRING_VALUE = "foo".toString();
}

Of course none of this is relevant if you don't have access to the code that defines the constants.

DJClayworth
  • 26,349
  • 9
  • 53
  • 79
  • +1; Bloch did cover this in Java Puzzlers. He uses `ident` instead of `toX`. – polygenelubricants Aug 19 '10 at 17:16
  • 1
    @DJClayworth: Nice. Didn't think of that :) For most types there are probably existing methods which will work (e.g. `String.valueOf()`) – Jon Skeet Aug 19 '10 at 18:09
  • 3
    I just remembered the assignment of `System.out` is this: `public final static PrintStream out = nullPrintStream();` (the actual stream is set later by native code). `nullPrintStream()` returns null if `currentTimeMillis()>0`. It never hit me why they did that (as opposed to just `...out = null;`) until now. Of course if I had bothered to read the documentation for `nullInputStream()` it would have been made clear... – Mark Peters Aug 19 '10 at 20:07
  • 3
    The inability to disable this optimization at compile time is one of many ways `javac` cripples the ability to create a proper debugger. – Sam Harwell Aug 29 '11 at 22:24
  • 1
    @MarkPeters that’s one of those esoteric optimizations, I love the Java core developers for. Since neither, objects of type `PrintStream` nor the `null` reference can ever be compile-time constants, this inlining can only refer to the JIT, but the JIT doesn’t care where the value came from before the assignment. Note that starting with Java 7, the code *does* look like `public final static PrintStream out = null;`. – Holger Jul 23 '19 at 11:10
  • 1
    Regarding fields which could truly be compile-time constants, like in this answer, it would be more efficient to use `public static final int INT_VALUE; public static final String STRING_VALUE; static { INT_VALUE = 1000; STRING_VALUE = "foo"; }` which avoids unnecessary method invocations, not to speak of the boxing overhead of `Integer.valueOf(1000)`. – Holger Jul 23 '19 at 11:11
14

I don't believe so. The simplest workaround would be to expose these as properties rather than fields:

public class A {
    private static final int INT_VALUE = 1000;
    private static final String STRING_VALUE = "foo";

    public static int getIntValue() {
        return INT_VALUE;
    }
    public static String getStringValue() {
        return STRING_VALUE;
    }
}

Don't forget that in certain cases the inlining is essential to the use of the value - for example, if you were to use INT_VALUE as a case in a switch block, that has to be specified as a constant value.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Jon, do you have any insight as to why the JLS suggests the accessor, but does not suggest privately using the final modifier (see my answer)? – Mark Peters Aug 19 '10 at 17:10
  • @Mark - Wouldn't it be possible for there to be inlining of the accessor function if the private member was `final` (the result could be determined at compile time), essentially recreating the `public static final` scenario? (speculative reasoning on my part) – Tim Stone Aug 19 '10 at 17:15
  • 3
    @Tim: My intuition would be that functions may only be inlined at runtime. Otherwise any stub method you create (`return null;`) would require you to recompile all classes utilizing it after you got around to implementing that method. – Mark Peters Aug 19 '10 at 17:17
  • @Mark - Ah, of course. Bit of a mental fail there, sorry about that. :) – Tim Stone Aug 19 '10 at 17:50
  • 1
    @Mark - one reason is the fact that no code sets the (private) field makes a `final` model redundant ... from a purely linguistic sense. (Style considerations say otherwise, but the JLS is not trying to be a style manual.) – Stephen C Aug 19 '10 at 17:56
9

To stop inlining you need to make the values non-compile time constants (the JLS term). You can do this without the use of functions and creating a minimum of bytecode by using a null in the initialiser expression.

public static final int INT_VALUE = null!=null?0: 1000;

Although it is very literal in its code generation, javac should optimise this to be a push of an immediate integer followed by a store to the static field in the static initialiser.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • How about just 1000+0. But would that then work in a case statement? – Tuntable Apr 04 '19 at 03:51
  • @Tuntable `1000+0` is a *compile-time constant*. It's no different from `1000` so it will work in a case statement, but then it will also inline. The **magic** in the answer is that `null` is not a *compile-time constant* and therefore no expression that uses it is either. – Tom Hawtin - tackline Apr 04 '19 at 11:14
  • 1
    If you want to be sure that the compiler just generates an assignment, you can simply write it as such, `public static final int INT_VALUE; static { INT_VALUE = 1000; }` – Holger Jul 23 '19 at 11:21
  • @Holger Whilst that's clearer way of producing exactly the same bytecode, I think there's more disadvantages. `null!=null?0:` clearly indicates an intent to do **something** so is less likely to be unplugged by a cleaner. Although the idiom isn't obvious, it shouldn't take long to work out what the result is, even if not the *compile-time constant* semantics*. It's not going to get totally mangled by reformatting. You don't have to match identifiers (do they match? don't they? Is it a weird spelling thing?). – Tom Hawtin - tackline Jul 23 '19 at 14:14
  • 1
    I didn’t insist on having it in one line; that’s just how comments are formatted here. So if a formatter breaks it into multiple lines, I’m fine with that. A cleaner, smart enough to understand that `null!=null` is meaningful, should have no problems with understanding the difference between compile-time constants and variables assigned in an initializer. Since a `static final` variable not immediately assigned *must* get assigned in a static initializer and it must get assigned exactly once, the compiler will check it. It’s the standard idiom for initializers not fitting into one expression. – Holger Jul 23 '19 at 14:29
8

JLS 13.4.9 deals with this issue. Their recommendation is to basically avoid compile-time constants if the value is in any way likely to change.

(One reason for requiring inlining of constants is that switch statements require constants on each case, and no two such constant values may be the same. The compiler checks for duplicate constant values in a switch statement at compile time; the class file format does not do symbolic linkage of case values.)

The best way to avoid problems with "inconstant constants" in widely-distributed code is to declare as compile time constants only values which truly are unlikely ever to change. Other than for true mathematical constants, we recommend that source code make very sparing use of class variables that are declared static and final. If the read-only nature of final is required, a better choice is to declare a private static variable and a suitable accessor method to get its value. Thus we recommend:

private static int N;
public static int getN() { return N; }

rather than:

public static final int N = ...;

There is no problem with:

public static int N = ...;

if N need not be read-only.

Mark Peters
  • 80,126
  • 17
  • 159
  • 190
3

I think this is a serious bug. Java is not C/C++. There is a principle(or not) "Compile once, run everywhere".

In this case, when Class A is changed. Any Classes referencing A.CONST_VALUE must be re-compiled and they hardly know whether Class A is changed.

neoedmund
  • 561
  • 7
  • 15
  • It's not a bug, it's a feature. It makes sure that if you have a constant in an object in another module that might not even be loaded, so when you ask for a BANANA, it does not go getting the Monkey class and the jungle package along with it. Java offers reflection and serialization(along with others) to avoid caching, but no mechanism to explicitly force inlining, so i'm euphoric it actually does inline it(if you delete the utility class with constants, the class using those constants will still work). Other solutions propose ways to do it just by using non compile time constants. – Dmytro Apr 14 '18 at 22:37
  • It is a bug. Remember that Java does JIT compiling, so it is a non-optimization that occasionally causes major grief. – Tuntable Apr 04 '19 at 03:49
  • 3
    @Tuntable it looks easy, when you only consider reading a `static` field instead of using a value. But imagine how a `switch` statement over constants of another class should get compiled, when it ought to support changes after compilation. Likewise, the language is strict when it comes to code reachability. Allowing constants to change after compilation would defeat the language safety. And it is consistent to force all uses of a constant to use the constant rather than to read a dynamic value. – Holger Jul 23 '19 at 11:18
  • @Holger The javac is not actually the java compiler. It is just the java parser. It is the JIT that optimizes. So interpreted CASE is just a hash table. Likewise, Java should have keyword optional parameters, just like every other pre-C language did, and efficiency is not an issue like it is with C++, say. Maybe we will see the latter given C# has had it for some time. – Tuntable Jul 24 '19 at 23:46
  • 1
    @Tuntable `javac` produces bytecode. That bytecode has to contain a compiled form of the switch table, regardless of how it looks like. Now tell, how the compiled form should look like, if the key values were allowed to change after compilation. You say “hash table”, but hash codes also depend on the actual value, hence, do not allow changing key values. Don’t try to distract from the topic with your “optional parameters” stuff. Does either, C++ or C#, allow the key values of a `switch` statement to change after compilation? I don’t think so. – Holger Jul 25 '19 at 07:47
  • @Holger, it would be just like an array initialized with expressions. Or a bunch of calls to a hash add function. Or some special byte code that the loader can resolve. And we now have Strings in hash tables which need a bit of work anyway. Lots of ways. But it don't, and every so often that trips people up badly. – Tuntable Jul 26 '19 at 09:17
  • 1
    @Tuntable the strings in `switch` statements still require *constant* labels. That’s how *switch* works. If you want “an array initialized with expressions” instead or some hashing structure that can cope with changing keys, well, just implement that. “Lots of ways” But don’t blame Java for not doing something entirely different than written into the source code. – Holger Jul 26 '19 at 09:22
  • @Holger, the common jump table optimization technique for switch statements needs constants by run time. But that is well after javac time. It could be load time. Or JIT time. Further, that optimization is neither necessary nor sufficient. A good compiler should use a jump table for "if (x==1) ... elseif (x==2)... elseif (x==3)...". Likewise a switch without constants that cannot be easily optimized is also very useful and supported in other languages. Java is much more sophisticated than C! – Tuntable Jul 28 '19 at 00:37
  • 2
    @Tuntable ok, I should have said “That’s how *Java’s* switch works”. It works that way and it requires constant labels. Yes, other languages support other constructs. But that never was the topic. The topic is constant fields in Java. The way, switch works in Java, the way, annotations work in Java, *and* the way, code reachability checks work in Java, mandate non-changing, resp. copied constant values, even if only implicit. Changing these, is not as trivial as with a simple field read. That’s the point. The mandatory code flow checks are way more interesting than the `switch` statement. – Holger Aug 05 '19 at 07:20
1

Rewrite class A like:

public class A {
    public static final int INT_VALUE;
    public static final String STRING_VALUE;

    static {
        INT_VALUE = 1000;
        STRING_VALUE = "foo";
    }
}
rveach
  • 2,081
  • 15
  • 25
0

I recently came across a similar issue, and, as it was said above, such inlining can be worked around using non-compile-time expressions, say:

public final class A {

    public static final int INT_VALUE = constOf(1000);
    public static final String STRING_VALUE = constOf("foo");

}

where the constOf method family is merely:

// @formatter:off
public static boolean constOf(final boolean value) { return value; }
public static byte constOf(final byte value) { return value; }
public static short constOf(final short value) { return value; }
public static int constOf(final int value) { return value; }
public static long constOf(final long value) { return value; }
public static float constOf(final float value) { return value; }
public static double constOf(final double value) { return value; }
public static char constOf(final char value) { return value; }
public static <T> T constOf(final T value) { return value; }
// @formatter:on

This is a bit shorter than other suggestions like Integer.valueOf(1000).intValue() or null!=null?0: 1000

Community
  • 1
  • 1
Lyubomyr Shaydariv
  • 20,327
  • 12
  • 64
  • 105
0

jmake is an open-source project that claims to do the whole job of keeping track of dependencies between Java files and incrementally compiling the minimum set of files required. It claims to correctly handle changes to static final constants, albeit by sometimes requiring the whole project to be recompiled. It even handles changes at a finer granularity than classfiles; if (for example) the signature of a method C.m() changes, then it only recompiles the classes that actually depend on m() rather than all classes that use C.

DISCLAIMER: I have no experience using jmake.

mhagger
  • 2,687
  • 18
  • 13
-5

I feel java tightly relies on dynamic compilation and it doesn't do any fancy compilation logic as like C++.

you can try out some options with JIT compilers which does run-time optimization which may have some options to disable/enable this.

in default javac you may not get that option. you have to use 1. some type of dependency graph like extends or implements 2. using method based linking.

-s

kadalamittai
  • 2,076
  • 1
  • 16
  • 19
  • 4
    I am not sure what you mean... This is strictly the javac (static compiler) behavior, and it seems one that's really dictated by the language spec at that. – sjlee Aug 19 '10 at 19:30