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.
Related
In the following code, readSide() refers to SIDE and when invoked to initialise another static variable, appears to get a value of zero, rather than the value assigned in SIDE's declaration.
import java.util.Random;
public class StaticTest {
private final static float SIDE_FROM_METHOD = readSide();
private final static float SIDE = 100.0f * new Random().nextFloat();
private static float readSide() {
System.out.println("In readSide(): SIDE=" + SIDE);
return SIDE;
}
public static void main(String[] args) {
System.out.println("In main(): SIDE_FROM_METHOD=" + SIDE_FROM_METHOD);
System.out.println("In main(): SIDE=" + SIDE);
System.out.println("In main(): readSide() return=" + readSide());
}
}
Sample output:
In readSide(): SIDE=0.0
In main(): SIDE_FROM_METHOD=0.0
In main(): SIDE=85.84305
In readSide(): SIDE=85.84305
In main(): readSide() return=85.84305
So although SIDE is supposed to be a constant, Java allows it to change value at runtime, rather than either throwing an exception or ensuring that initialisation order is done in dependency order.
I believe the source of the problematic behavior is the order of defining the "final static" objects, but why doesn't it cause exception instead of working like that?
If SIDE is initialised to a literal rather than a calculated value:
private final static float SIDE=100.0f;
... then the output is 100.0 throughout.
In readSide(): SIDE=100.0
In main(): SIDE_FROM_METHOD=100.0
In main(): SIDE=100.0
In readSide(): SIDE=100.0
In main(): readSide() return=100.0
Why does this make a difference?
You have stumbled on some fairly subtle behaviour, hinging on the definition of a constant.
Most of the time we assume that any final static variable is a "constant", and this is good enough for us to get on with programming.
However the Java Language Specification states in section 4.12.4:
(emphasis mine)
A variable of primitive type or type String, that is final and
initialized with a compile-time constant expression (§15.28), is
called a constant variable.
Whether a variable is a constant variable or not may have implications
with respect to class initialization (§12.4.1), binary compatibility
(§13.1, §13.4.9) and definite assignment (§16).
... and we have to jump to another section to find out what they mean by "compile-time constant expression". In a nutshell though, 100.0f is a compile-time constant expression. So is 100.0f * 10. 100.0f * new Random().nextFloat() is not, because it cannot be calculated at compile time.
In your first example, SIDE is not strictly a constant, and so initialisation happens in a different order - the details are in JLS 12.4.1.
When you change the program to make SIDE = 100f, it becomes a bona-fide compile-time constant, and hence the rules change and it gets assigned at compile-time.
Once you know, it's easy to work around these issues. As you've observed, just putting the assignments in the right order will fix the symptoms.
The strange behaviour is caused by the fact that when private final static float[] POINTS = generatePoints(); the SIDE constant has not been initialised. To fix the problem just move that line down to below the initialisation of SIDE.
The reason there is no exception is because SIDE will hold the value 0.0 before initialisation so all that goes wrong is that the calculations in generatePoints reads that value as 0.
See JLS 4.12.5 for what values are used for each type.
How does this compiles without error? As my understanding, the compiler checks the type of the variable (in this case String), then sees if the type of the expression on the right side corresponds to the variable's type (or at least a subtype but let's stick to the simple case with the String class since it's final).
public class InitClass {
public static void main(String[] args) {
String str = (str = "hello");
System.out.println(str);
}
}
My question is how does str = "hello" compile? Is the compiler already aware that str should be of type String?
When evaluating an assignment expression
First, the left-hand operand is evaluated to produce a variable. If
this evaluation completes abruptly, then the assignment expression
completes abruptly for the same reason; the right-hand operand is not
evaluated and no assignment occurs.
That produces the variable str. Then
Otherwise, the right-hand operand is evaluated. If this evaluation
completes abruptly, then the assignment expression completes abruptly
for the same reason and no assignment occurs.
In your example, the right hand operand is itself another assignment expression. So str, the right hand operand of the assignment operator, is again evaluated to produce a variable, str. Then
Otherwise, the value of the right-hand operand is converted to the
type of the left-hand variable, is subjected to value set conversion
(§5.1.13) to the appropriate standard value set (not an
extended-exponent value set), and the result of the conversion is
stored into the variable.
So "hello" is stored into str. And since
At run time, the result of the assignment expression is the value of
the variable after the assignment has occurred. The result of an
assignment expression is not itself a variable.
the result of the assignment of "hello" to str is the value "hello", that value is again stored in str.
Your case is equivalent to
String str;
str = (str = "hello");
Although the assignments are funny looking, there's nothing wrong conceptually.
Nevertheless, a variable initialization that references itself is obviously not a good idea. The compiler will try to flag it in situations that's very likely a programer error; the compiler fails to do so some times; and may also go overboard some other times.
A local variable has a stricter requirement (than a field variable) - it must be assigned first before its value is used. For example, this won't compile, because the local var is read before it's assigned.
String str; // local variable
str = str; // error, try to read `str` before it's assigned
A field variable always has a default initial value; nevertheless, the compiler checks for apparent programer mistakes
int x = x+1; // error. x is field variable.
But it's not catastrophic if such checking fails, since x has a value 0 before explicit assignment
int x;
{ x=x+1; } // ok. x==0, then x==1 after assignment
However, if x is final, the code above fails, because compiler requires definite assignment of x first before reading x, the same requirement for a local variable. But this checking can be circumvented, because it's impossible to analyze and prevent it fully for field variables
final int x = (this).x+1; // compiles!
In some cases, the compiler goes overboard, preventing legit use cases involving lambda
Runnable r1 = ()->System.out.println(r1); // r1 is a field variable
There's nothing conceptually problematic in this use case; it can be circumvented by (this). too.
What first happens is that the compiler identify the type of the reference then knowing that is a String assign "hello" to str is valid.
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.
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.
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