Home > Mobile >  Does Java Evaluate a Variable Declared as Final only Once?
Does Java Evaluate a Variable Declared as Final only Once?

Time:03-15

I'm writing a Java program that requires thousands of System.out.println() statements that will be printed hundreds of millions (or billions) of times throughout the lifecycle of the program for debugging purposes:

if (GVar.runInDebugMode) System.out.println("Print debug message");

In the real world, these statements can be deactivated in order to speed up a computational heavy calculation.

If I set:

public final static boolean runInDebugMode = false;

Does the compiler re-evaluate runInDebugMode each time it comes across a statement like: if (GVar.runInDebugMode) or since it was declared as final it will be evaluated once at the beginning of the program and won't put additional strain on the CPU? In other words, would I be better off commenting out all debug statements entirely once I deploy the app or is setting runInDebugMode to false sufficient?

CodePudding user response:

The scenario you have is described as "conditional compilation", i.e. the compiler that produces bytecode can optimize away the code if the constant variable is false:

"...in order to allow the if statement to be used conveniently for "conditional compilation" purposes, the actual rules differ.

As an example, the following statement results in a compile-time error:

while (false) { x=3; }

because the statement x=3; is not reachable; but the superficially similar case:

if (false) { x=3; }

does not result in a compile-time error. An optimizing compiler may realize that the statement x=3; will never be executed and may choose to omit the code for that statement from the generated class file, but the statement x=3; is not regarded as "unreachable" in the technical sense specified here."

Note the part in bold: it depends on the compiler but it's quite easy to verify with the javap disassembler, just run javap -v -l -p MyClass.class to view the resulting bytecode. I'm pretty sure the Oracle/OpenJDK javac does that optimization since long ago and so do most compilers. Note that GVar.runInDebugMode is a constant expression, so it benefits from this optimization.

Keep in mind:

"Conditional compilation comes with a caveat. If a set of classes that use a "flag" variable - or more precisely, any static constant variable (§4.12.4) - are compiled and conditional code is omitted, it does not suffice later to distribute just a new version of the class or interface that contains the definition of the flag. The classes that use the flag will not see its new value, so their behavior may be surprising. In essence, a change to the value of a flag is binary compatible with pre-existing binaries (no LinkageError occurs) but not behaviorally compatible."

which basically says that you have to also re-compile the code that uses the flag, in addition to the class that defines it. This shouldn't be a problem if you build the whole code.

CodePudding user response:

When you declare a variable like

public final static boolean runInDebugMode = false;

it’s a compile-time constant.

A constant variable is a final variable of primitive type or type String that is initialized with a constant expression (§15.29).

which means that

A reference to a field that is a constant variable (§4.12.4) must be resolved at compile time to the value V denoted by the constant variable's initializer.

If such a field is static, then no reference to the field should be present in the code in a binary file, including the class or interface which declared the field.

In other words, when you write if(runInDebugMode) anywhere and runInDebugMode is false at compile time, the behavior is as if you’ve written if(false), as the value must be resolved at compile time and no reference to the field appears in the compiled class file.

Your use case has been discussed specifically in §14.22

However, in order to allow the if statement to be used conveniently for "conditional compilation" purposes, the actual rules differ.

As an example, the following statement results in a compile-time error:

while (false) { x=3; }

because the statement x=3; is not reachable; but the superficially similar case:

if (false) { x=3; }

does not result in a compile-time error. An optimizing compiler may realize that the statement x=3; will never be executed and may choose to omit the code for that statement from the generated class file, but the statement x=3; is not regarded as "unreachable" in the technical sense specified here.

The rationale for this differing treatment is to allow programmers to define "flag" variables such as:

static final boolean DEBUG = false;

and then write code such as:

if (DEBUG) { x=3; }

The idea is that it should be possible to change the value of DEBUG from false to true or from true to false and then compile the code correctly with no other changes to the program text.

Conditional compilation comes with a caveat. If a set of classes that use a "flag" variable - or more precisely, any static constant variable (§4.12.4) - are compiled and conditional code is omitted, it does not suffice later to distribute just a new version of the class or interface that contains the definition of the flag.

So, this statement makes clear that this form of conditional compilation matches the intent of the language designers and that compilers are entitled to omit the code in question (all relevant compilers do). In principle, a compiler is not required to omit the code, but since it must not generate a reference to the field GVar.runInDebugMode in the compiled code, the code can’t contain a real conditional. If the code is not omitted, it must be skipped in a de-facto unconditional way. Either, by a goto instruction or, when compiling in the most naïve way imaginable, by literally testing false, iconst_0; ifeq …. Both approaches would be on the nanosecond scale in interpreted execution mode and no challenge to the JIT compiler/ optimizer at all.


It’s worth mentioning that static final fields are trusted fields which are normally not even changeable by Reflection. This is used, e.g. by the Assertion feature, as under the hood, a class containing an assert statement will have a static final boolean field initialized at class initialization time (so it’s not a compile-time constant) and each assert statement will skip its check conditionally, depending on the state of the static final variable. It was as early as at Java 1.4 time, when it was concluded that the necessary dead code elimination is commonplace in JVMs, to rely on it in this way.

So even if you turn your debug flag from compile-time constant to an initialization-time constant, the impact on the performance would be hardly noticeable. But the way you’re using it now, the code is removed at compile-time already and doesn’t rely on the JVM anyway.

  • Related