What is the difference between final variables and compile time constants?
Consider the following code
final int a = 5;
final int b;
b=6;
int x=0;
switch(x)
{
case a: //no error
case b: //compiler error
}
What does this mean? When and how are final variables assigned a value? What happens at run time and what happens at compile time? Why should we give switch a compile time constant? What other structures of java demands a compile time constant?
The problem is, that all case: statements must be ultimate at compile time.
Your first statement is ultimate. a will for 100% be no other value than 5.
final int a = 5;
However, this is not guaranteed for b. What if there would be an if-statement around b?
final int b;
if(something())
b=6;
else
b=5;
What does this mean?
It means that 'b' isn't a compile time constant expression, and the JLS requires it to be.
When and how are final variables assigned a value?
Formally, when the assignment statement or initializer is executed.
But in practice, if the final declares a compile time constant the expression is evaluated at compile time and its value is hard-wired into the code.
What happens at run time and what happens at compile time?
See above.
Why should we give switch a compile time constant?
Because the JLS requires it.
It is necessary for the bytecode compiler to check that the switch statement is well formed; i.e. that the values of the switch constants don't collide. It also allows the JIT compiler to generate code that is optimized for the actual values of the switch constants.
What other structures of java demands a compile time constant?
None that I can think of, off the top of my head.
From compiler point of view you are trying to use a variable b that might not be initialized. The switch statement are compiled into JVM bytecode tableswitch or lookupswitch which requires that values used in case statement are both compile time constant and unique.
final int a = 4; // compiler is sure a is initialized
final int b;// variable b is not guranted to be assigned
e.g.
Although this statement will ultimately initialize b , but compiler can't detect it.
if (a < 4) b= 10;
if (a >= 4) b = 8
final int b; can be assigned once and value is not sure, that will be decided on runtime depending on conditions. that is the reason, even if being a final variable, it is not COMPILE TIME constant although it will be a RUN TIME constant and case needs compile time constants.
The switch statement needs a constant. As final variables can be delayed initialized and the compiler cannot determine for b that it has a value in the case branch.
Related
class Foo{
public static void main(String args[]){
final int x=101;
int y;
if(x>100){
y=-1;
}
System.out.println(y);
}
}
Java compiler understands the condition of the if statement is always true and therefore y will always be initialized. No compile error, as expected.
class Bar{
public static void main(String args[]){
final int x;
x=101;
int y;
if(x>100){
y=-1;
}
System.out.println(y);
}
}
But when I break the declaration and initialization of x into two lines, the compiler does not seem to get that the condition is always true and y will always be initialized.
final int x;
x=101;
byte b;
b=x;
System.out.println(b);
Same thing happens here and the compiler gives a loss of precision error.
final int x=101;
byte b;
b=x;
System.out.println(b);
Again, the compiler can understand that x is inside the range of b.
As part of aiming for portability, there is a very specific set of rules for what a compiler should accept and what it should reject. Those rules both permit and require only a limited form of flow analysis when determining whether a variable is definitely assigned at its use.
See the Java Language Specification Chapter 16. Definite Assignment
The critical rule is the one in 16.2.7. if Statements, "if (e) S" case. The rule for being definitely assigned expands to:
V is assigned after if (e) S if, and only if, V is assigned after S and V is assigned after e when false.
y is the relevant V. It is unassigned before the if statement. It is indeed assigned after S, y = {y=-1;} but there is nothing making it assigned when x>100 is false.
Thus y is not definitely assigned after the if statement.
A more complete flow analysis would determine that the condition x>100 is always true, but the compiler is required by the JLS to reject the program based on these specific rules.
The final variable is fine. The rule is actually: -
"It is a compile-time error if a final variable is assigned to unless
it is definitely unassigned (§16) immediately prior to the
assignment."
The declaration leaves it definitely unassigned, and even the limited flow analysis can determine that x is still definitely unassigned at the assignment.
It has to do with how the compiler determines if a statement will be executed or not. It is defined in the JLS #16:
Each local variable and every blank final field must have a definitely assigned value when any access of its value occurs.
In your case, the compiler can't determine that y has been definitely assigned and gives you an error. This is because it would need to determine that the condition is always true and that is only possible if the condition in the if is a constant expression.
JLS #15.28 defines constant expressions:
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:
[...]
Simple names (§6.5.6.1) that refer to constant variables (§4.12.4).
The JLS #4.12.4 defines constants variables as:
A variable of primitive type or type String, that is final and initialized with a compile-time constant expression, is called a constant variable.
In your case, final int x = 101; is a constant variable but final int x; x = 101; is not.
What have you done for the variable x in the second code is called blank final variable. If a final variable is not initialized when it is declared, then it is known as a blank final variable.
Many Java developers think that value of a final variable is known in the compile time. This is NOT always true. It is said that value of a blank final variable NOT known at the compile time. Hence your second code will give you a compile error. Compiler can see that you have initialized the final variable x, but compile doesn't know it's value. So compiler can't resolve the if statement. Therefor it thinks that variable y is not initialized.
You can read more about Java final variables here.
In Java 7, for the given code:
final Integer i=9;
final int x=5;
switch(x){
case 1:
case i://compilation error is thrown here
}
What is the reason behind this?
Integer i = 9;
With this i is now a reference to Integer object and it's not a valid type to switch on in Java.
Following are the valid variable types, you can Switch on
Convertible ints - int, byte,short,char
Enums
String constants - support added in Java 7
Other than these valid values, you cannot just switch on any other Object
This is the reason I was looking for:
Wrapper objects such as Integer cannot be used in a case statement as they are not compile time constants(because the boxing and un boxing happens at runtime). hence you can only use primitives that are compile time constants which
must also be final.
In many languages switch statements represent a constant time lookup over a set of compile time constants.
The javac compiler will transform the switch statement into a very efficient bytecode representation and for that optimization the compile time constant is necessary.
1) switch case doesnt support any Object type as case, more over it supports primitive variables as constants and literals.
2) From Java 7 it supports Strings.
ex:-
final int num1=10;
int x=0;
int num2=5;
final Integer y=20;
switch (num2) {
case num1: //logic
case x ://logic ----> Compile time Error since it is not final/constant.
case y ://logic ----> Compile time Error,since it is of object type.
default:
//logic
}
When I run the javac compiler on the following code -
void method() {
final int x;
x = 1;
x = 1; // (Intentional error)
}
I receive the following error -
..\src\pkgs\main\Main.java:60: error: variable x might already have been assigned
x = 1; // (Intentional error)
^
I wonder why this error message uses the word "might". Would a more accurate description use the word "has" instead, as in "has already been assigned"? Is there a particular reason why the compiler seems to use a kind of "vague description style" for this type of error? Thanks.
A final variable can only be assigned if it is definitely unassigned. That is, "might" is referring to the fact that the variable is not definitely unassigned:
Similarly, every blank final variable must be assigned at most once; it must be definitely unassigned when an assignment to it occurs.
For instance, consider this code which makes the wording of "might" more clear:
final int x;
if (maybeTrueOrMaybeFalse()) {
x = 1;
}
/* error: variable x might already have been assigned */
x = 1;
The standard Sun/Oracle javac compiler produces the same error message for the "might" (not definitely unassigned) and "has been" (definitely assigned) cases. A different compiler or code analysis tool could very well provide a different/refined message in the "has been" case.
If you have initialized it when declaring, it would give you a precise message, e.g.
"The final local variable x cannot be assigned. It must be blank and
not using a compound assignment"
final int x = 6;
// code
x = 4;
But, in your example, the final variable might have been initialized somewhere after it is declared and the compiler doesn't keep track of consecutive statements, otherwise it would be named "runner" and it just has the information that final variable x should not be initialized twice.
The Java language documentation says:
If a primitive type or a string is defined as a constant and the value
is known at compile time, the compiler replaces the constant name
everywhere in the code with its value. This is called a compile-time
constant.
My understanding is if we have a piece of code:
private final int x = 10;
Then, the compiler will replace every occurrence of x in the code with literal 10.
But suppose the constant is initialized at run-time:
private final int x = getX(); // here getX() returns an integer value at run-time.
Will there be any performance drop (howsoever negligible it may be) compared to the compile-time constant?
Another question is whether the below line of code:
private int y = 10; // here y is not final
is treated in same way as compile-time constant by the compiler?
Finally, what I understand from the answers are:
final static means compile-time constant
just final means it's a constant but is initialized at run-time
just static means initialized at run-time
without final is a variable and wouldn't be treated as constant.
Is my understanding correct?
Compile time constant must be:
declared final
primitive or String
initialized within declaration
initialized with constant expression
So private final int x = getX(); is not constant.
To the second question private int y = 10; is not constant (non-final in this case), so optimizer cannot be sure that the value would not change in the future. So it cannot optimize it as good as constant value. The answer is: No, it is not treated the same way as compile time constant.
The JLS makes the following distinctions between final variables and constants:
final variables
A variable can be declared final. A final variable may only be
assigned to once. It is a compile-time error if a final variable is
assigned to unless it is definitely unassigned immediately prior to
the assignment (§16 (Definite Assignment)).
Once a final variable has been assigned, it always contains the same
value. If a final variable holds a reference to an object, then the
state of the object may be changed by operations on the object, but
the variable will always refer to the same object. This applies also
to arrays, because arrays are objects; if a final variable holds a
reference to an array, then the components of the array may be changed
by operations on the array, but the variable will always refer to the
same array.
A blank final is a final variable whose declaration lacks an
initializer.
constants
A constant variable is a final variable of primitive type or type
String that is initialized with a constant expression (§15.28).
From this definition, we can discern that a constant must be:
declared final
of primitive type or type String
initialized within its declaration (not a blank final)
initialized with a constant expression
What about compile-time constants?
The JLS does not contain the phrase compile-time constant. However, programmers often use the terms compile-time constant and constant interchangeably.
If a final variable does not meet the criteria outlined above to be considered a constant, it should technically be referred to as a final variable.
According to JLS, there is no requirement that "constant variable" should be static.
So "constant variable" maybe static or non-static (instance variable).
But JLS imposes some other requirements for a variable to be a "constant variable" (besides being just final):
being only String or primitive
initialized inline only, because it is final, and blank final is not allowed
initialized with "constant expression" = "compile-time constant expression" (see JLS quote below)
4.12.4. final Variables (JLS)
A constant variable is a final variable of primitive type or type String that is initialized with a constant expression (§15.28).
15.28. Constant Expressions
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.1,
§3.10.2, §3.10.3, §3.10.4, §3.10.5)
Casts to primitive types and casts to type String (§15.16)
The unary operators +, -, ~, and ! (but not ++ or --) (§15.15.3,
§15.15.4, §15.15.5, §15.15.6)
The multiplicative operators *, /, and % (§15.17)
The additive operators + and - (§15.18)
The shift operators <<, >>, and >>> (§15.19)
The relational operators <, <=, >, and >= (but not instanceof)
(§15.20)
The equality operators == and != (§15.21)
The bitwise and logical operators &, ^, and | (§15.22)
The conditional-and operator && and the conditional-or operator ||
(§15.23, §15.24)
The ternary conditional operator ? : (§15.25)
Parenthesized expressions (§15.8.5) whose contained expression is a
constant expression.
Simple names (§6.5.6.1) that refer to constant variables (§4.12.4).
Qualified names (§6.5.6.2) of the form TypeName . Identifier that
refer to constant variables (§4.12.4).
There might be a really small performance drop on some machines for private final int x = getX(); since that would involve at least one method call (besides the fact that this isn't a compile-time constant) but as you said, it would be negligible so why bother?
As for the second question: y isn't final and thus is not a compile time constant, since it might change at runtime.
The final keyword means that a variable will be initialized once and only once. A real constant need to be declared static as well.
So, none of your examples are treated as constants by the compiler. Nevertheless, the final keyword tells you (and to the compiler) that your variables will be initialized once only (in the constructor or literally).
If you need their values assigned at compile time your fields must be static.
Performance is not really that affected, but have in mind that primitive types are immutable, once you have created one it will hold that value in memory until the garbage collector removes it.
So, if you have a variable y = 1; and then you change it to y = 2; in memory the JVM will have both values, but your variable will "point" to the latter.
private int y = 10; // here y is not final
is treated in same way as compile time constant by the compiler ?
No. This is an instance variable, created, initialized an used at runtime.
Just keep in mind that in the following code, x is not compile time constant:
public static void main(String[] args) {
final int x;
x= 5;
}
private final int x = getX();
Will be called the first time your object is declared. The performance "drop" will depend on getX() but that's not the kind of things to create some bottleneck.
Simply speaking while compilation the compiler replaces the reference with the actual value specified, instead of using the reference parameter.
public static void main(String[] args) {
final int x = 5;
}
ie. while compilation the complier take the initialised value of 5 directly for compliation than using the reference variable 'x';
Please check this explanation
int a = 1, b;
if(a > 0) b = 1;
if(a <= 0) b = 2;
System.out.println(b);
If I run this, I receive:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The local variable b may not have been initialized
at Broom.main(Broom.java:9)
I know that the local variables are not initialized and is your duty to do this, but in this case, the first if doesn't initialize the variable?
If you change the second if to else, then the compiler would be happy.
int a = 1, b;
if(a > 0) b = 1;
else b = 2;
System.out.println(b);
If you really want to go deep into this matter, one whole chapter of the Java Language Specification is dedicated to the issue of Definite Assignment. This case pertains to your specific example:
the rules do not accept the variation:
void flow(boolean flag) {
int k;
if (flag)
k = 3;
if (!flag)
k = 4;
System.out.println(k); // k is not "definitely assigned" before here
}
and so compiling this program must cause a compile-time error to occur.
This particular example (and many other illustrative ones) may seem to defy your expectation, but this is exactly the way the language designers wanted it, and all compilers must abide by the rules.
Focus on "IF", The compiler can't tell if the condition will be true.
Please don't try to run your code when it doesn't even compile.
Usually you couldn't do that anyway, but modern IDEs are so "helpful" to allow you to do that. They usually replace uncompilable parts of the code with code that just throws an error such as the one you see.
The much better approach is to look at the error messages your compiler/IDE gives you and try to fix those before you try to run your application.
Learning the difference between compiler errors and runtime exceptions is an important step to take.
In Java a local variable must be initialized before its used.
In your case both the initializations are conditional and the compiler cannot determine if any of the conditionals will be true. This upsets the compiler.
From Java docs:
A local variable (§14.4, §14.13) must be explicitly given a value before it is used, by either initialization (§14.4) or assignment (§15.26), in a way that can be verified by the compiler using the rules for definite assignment
Local variables are not replaced in compile time so the compiler have no idea if the IF is true or false.
On the other hand if the variable was defined final then it will be replaced during compile time.
Java compiler cannot find out that the other if works as an else. Compilers are smart but not that smart.
You could add the final keyword to the declaration of a to help your compiler optimizing the code.
// this compiles just fine
final int a = 1, b;
if(a > 0) b = 1;
if(a <= 0) b = 2;
System.out.println(b);