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
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.
Have seen following threads:
Java8 Stream compiler message -- local variable must be final or effectively final
Variable assignment in lambda expression
According to JavaDoc
Any local variable, formal parameter, or exception parameter used but
not declared in a lambda expression must either be declared final or
be effectively final (§4.12.4), or a compile-time error occurs where
the use is attempted.
Any local variable used but not declared in a lambda body must be
definitely assigned (§16 (Definite Assignment)) before the lambda
body, or a compile-time error occurs.
Similar rules on variable use apply in the body of an inner class
(§8.1.3). The restriction to effectively final variables prohibits
access to dynamically-changing local variables, whose capture would
likely introduce concurrency problems. Compared to the final
restriction, it reduces the clerical burden on programmers.
Is arrays an exception to rule 1?
A sample program that validates javadoc:
List<Integer> li = Arrays.asList(1,2,3,45,678);
final int v = 2;
li.stream().filter(e-> e!=v).map(e->e).forEach(System.out::println);
v= 5;
compilation error at line v=5; , obviously
A sample snippet that is violating the final assignment rule:
List<Integer> li = Arrays.asList(1,2,3,45,678);
final int[] v = {2};
li.stream().filter(e-> e!=v[0]).map(e->e).forEach(System.out::println);
v[0]= 5;
Output:
3
45
678
Above snippet is giving no compilation error, am i missing something??
In the first case v is variable of type int, here value of v is 2 and it is final variable. when you try to assign 5 it is giving error because you can't change final variable value(Working as expected).
Comes to second case, v in not a variable of type int, it is an array. In Java arrays are objects, so here v is a reference. Generally a reference refers to an object and contains the address of the object. When your trying to do v[0] = 5 here your changing the value inside the object but not the value of the reference. If you try to do v = new int[1] or v={5} then you will get compilation error
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.
This is more of a theory question than a solution question, so hear me out.
In C/C++ as well as PHP, you can declare constants. There are usually a couple of ways to do this (#DEFINE for example, or 'const type'...) and the ultimate effect of this is that during compilation a replace is done so that all of those named constants become literals. This helps because instead of having to access a memory location to find the data, the data is hardcoded in, but without the downside of hardcoding - recalling the value if it needs to be reused, and changing all of the instances of that value when it needs to be changed.
But Java's final declaration is slightly inscrutable; because I can create a class with unset final vars and initialize them on construction, it means that they are not precompiled as literals as far as I can tell. Other than guaranteeing that they cannot logically change afte construction, does the final declaration provide any benefit to efficiency?
References to articles are fine, as long as you make note of the part which explains what final really does and what are if any its benefits other than stopping value changes after construction.
As a corollary, is it possible to actually declare compilation-level constants in Java in any other way than simply using literals (a bad idea anyway?)
Java does have constant expressions. See here in the java language specification.
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 -
The shift operators <<, >>, and >>>
The relational operators <, <=, >, and >= (but not instanceof)
The equality operators == and !=
The bitwise and logical operators &, ^, and |
The conditional-and operator && and the conditional-or operator ||
The ternary conditional operator ? :
Parenthesized expressions whose contained expression is a constant expression.
Simple names that refer to constant variables (§4.12.4).
Qualified names of the form TypeName . Identifier that refer to constant variables (§4.12.4).
But in Java, unlike C/C++, you also have a JIT compiler, so additional inlining is possible. So the bottom line is, don't worry about until you see a real performance problem.
Java does have constants that the Java compiler will replace with their values at compile-time. For example, member variables that are final and that are of type String are effectively constants which are replaced in this way. (This is allowed because class String is immutable). One of the consequences of this is that if you change the string in your source code, but you don't recompile the classes where this string is used, those classes will not see the new value of the string.
The JLS explains this in the following paragraphs:
4.12.4 final variables
13.4.9 final Fields and Constants
Final fields are aimed to make immutable objects.
Static final fields are your kind of constants.
Compiler optimisation, data flow analysis, happens to some degree. Try javap to see the jvm byte codes - if your are interested that far.
does the final declaration provide any benefit to efficiency?
Not really. This is because the JIT can often determine that the value is not changed at runtime and can treat it as final. (Which is a problem if the value is not volatile and is changed in another thread)
In Java 8, you can use local variables in closures if they could be made final, rather than having to declare them as final.
I would like to define some constants like #define myXYZ 1 so I can also use them in switch statements.
When I do the often suggested
public static final Integer myXYZ = 1;
and
case Constants.myXYZ:
I get the compiler message that case expression must be constant
What would be the best way of resolving this?
Thanks!
Just change it to int and it should be fine:
public static final int myXYZ = 1;
This is because a case value has to be a constant expression or an enum name. From section 15.28 of the JLS (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 [...]
Therefore an expression of type int can be a constant expression, but an expression of type Integer can't.
If this is a set of values which makes sense as a concept on its own, then an enum would possibly make more sense.
Use int :
public static final int myXYZ = 1;
or use enum constants.
If you want to switch between them probably best design it to use enumeration instead