views:

200

answers:

5

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 int 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.

+5  A: 

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
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
@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
@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
@Mark - Ah, of course. Bit of a mental fail there, sorry about that. :)
Tim Stone
@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
+1  A: 

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
+8  A: 

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 = toInt(1000);
  public static final String STRING_VALUE = toString("foo");

  private static int toInt(int i) {
    return i;
  }

  private static String toString(String s) {
    return s;
  }

}

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

DJClayworth
+1; Bloch did cover this in Java Puzzlers. He uses `ident` instead of `toX`.
polygenelubricants
I needed two methods to show declaring constants of two types, so I renamed them rather than using overloading.
DJClayworth
@DJClayworth: Nice. Didn't think of that :) For most types there are probably existing methods which will work (e.g. `String.valueOf()`)
Jon Skeet
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
A: 

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
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
+1  A: 

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