views:

415

answers:

6

Consider this case:

public Class1 {
   public static final String ONE = "ABC";
   public static final String TWO = "DEF";
}

public Class2 {

  public void someMethod() {
    System.out.println(Class1.ONE + Class1.TWO);
  }
}

Typically you would expect the compiler to inline the ONE and TWO constants. However, is this behavior guaranteed? Can you deploy at runtime Class2 without Class1 in the classpath, and expect it to work regardless of compilers, or is this an optional compiler optimization?

EDIT: Why on earth do this? Well I have a constant that would be shared between two ends of an application (client and server over RMI) and it would be very convenient in this particular case to put the constant on a class that can only be on one side of that divide (as it is logically the one that owns that constant value) rather than have it in an arbitrary constants class just because it needs to be shared by both sides of the code. At compile time its all one set of source files, but at build time it is divided by package.

+7  A: 

It's guaranteed to be treated as a constant expression, and guaranteed to be interned by section 15.28 of the JLS:

A compile-time constant expression is an expression denoting a value of primitive type or a String that does not complete abruptly and is composed using only the following:

  • Literals of primitive type and literals of type String (§3.10.5)
  • Casts to primitive types and casts to type String
  • The unary operators +, -, ~, and ! (but not ++ or --)
  • The multiplicative operators *, /, and %
  • The additive operators + and -
  • ...

...

Compile-time constants of type String are always "interned" so as to share unique instances, using the method String.intern.

Now, that doesn't quite say it's guaranteed to be inlined. However, section 13.1 of the spec says:

References to fields that are constant variables (§4.12.4) are resolved at compile time to the constant value that is denoted. No reference to such a constant field should be present in the code in a binary file (except in the class or interface containing the constant field, which will have code to initialize it), and such constant fields must always appear to have been initialized; the default initial value for the type of such a field must never be observed.

In other words, even if the expression itself weren't a constant, there should be no reference to Class1. So yes, you're okay. That doesn't necessarily guarantee that the concatenated value is used in the bytecode, but the bits referenced earlier guarantee that the concatenated value is interned, so I'd be hugely surprised if it didn't just inline the concatenated value. Even if it doesn't, you're guaranteed that it'll work without Class1.

Jon Skeet
Thanks Jon, but that isn't exactly what I meant. "interned" means that there is only one instance, but you could still get a NoClassDefError if the class is missing at runtime if it wasn't actually inlined by the compiler.
Yishai
Maybe I'm blind but where does it say that?
gshauger
@gshaugher: Which bit?
Jon Skeet
@Yishai: I believe that in order to guarantee that it's interned, it would have to be inlined too... will see if I can find a full guarantee though. Will edit answer.
Jon Skeet
Section 13.1 of JLS guarantees inlining for constant variables: "References to fields that are constant variables (§4.12.4) are resolved at compile time to the constant value that is denoted. No reference to such a constant field should be present in the code in a binary file (except in the class or interface containing the constant field, which will have code to initialize it), and such constant fields must always appear to have been initialized; the default initial value for the type of such a field must never be observed. See §13.4.8 for a discussion."
bkail
@bkail: Thanks - that's definitely enough to guarantee that the code will be okay, even if the value isn't inlined. Have edited my answer.
Jon Skeet
A: 

It won't be inlined by the compiler but by the interpreter at runtime and if possible converted to assembly code.

It cannot be guaranteed, because not all the interpreters ( JVM's ) work the same way. But the most important implementations will do.

Unfortunately I don't have a link to sustain this :(

OscarRyz
A: 

I suspect, but don't know for sure, that this will work, but it doesn't sound like a good idea.

The "normal" ways to do this are:

  1. Put the constants in a package that's shared between the client and the server. Presumably, there is such a package, because that's where the interfaces go.
  2. If there's no such package, create 2 classes with the shared constants: one for the server and one for the client.
ykaganovich
A: 

See JLS 13.4.9. While it does not explicitly require that constants are inlined by the compiler, it hints that conditional compilation and support for constants in switch statements cause the compiler to always inline constants.

Yardena
+2  A: 

Compiling that with javac 1.6.0_14 produces the following bytecode:

public void someMethod();
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   ldc     #3; //String ABCDEF
   5:   invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:   return

So the strings are concatenated at compile time and the result is included in Class2's constant pool.

Esko Luontola
+1, thanks for the research, but I did know that the compiler behaved that way. My question was if the spec guaranteed that or if that was optional behavior (and therefore may come back to bite later).
Yishai
A: 

It looks like you're coding your own version of the capability built into enum, which does public static final for you, proper naming via name() and toString() (as well as having some other advantages, but perhaps having the disadvantage of a larger memory footprint).

Are you using an older version of Java that doesn't include enum yet?

Carl
My use case represents context roots and urls, so it is a string straight up. The purpose of making a constant is to avoid duplicating it in case it changes. So Enums would be overkill in this case. I'm using Java 1.5, so I do have Enums and use them (perhaps not often enough).
Yishai