Before java 8 an inner class could access outer objects only if they were declared final.
However now when I run example code (from below) on javaSE 1.8 there is no compilation error and program runs fine.
Why did they change that and how does It work now?
Example code from java 7 tutorial:
public class MOuter {
private int m = (int) (Math.random() * 100);
public static void main(String[] args) {
MOuter that = new MOuter();
that.go((int) (Math.random() * 100), (int) (Math.random() * 100));
}
public void go(int x, final int y){
int a = x + y;
final int b = x - y;
class MInner{
public void method(){
System.out.println("m is "+m);
System.out.println("x is "+x); // supposedly illegal - 'x' not final
System.out.println("y is: "+y);
System.out.println("a is "+a); // supposedly illegal? - 'a' not final
}
}
MInner that = new MInner();
that.method();
}
}
In Java 7 the the concept of effectively final was introduced to support the "more precise rethrow" feature, and its scope was expanded in Java 8 to cover local variables that are assigned only once, but not actually declared final. These can be captured and used in lambda bodies or inner classes just as if they were declared final.
This is covered in section §4.12.4 of the Java Language Specification:
Certain variables that are not declared final may instead be
considered effectively final.
A local variable or a method, constructor, lambda, or exception
parameter is effectively final if it is not declared final but it
never occurs as the left hand operand of an assignment operator
(§15.26) or as the operand of a prefix or postfix increment or
decrement operator (§15.14, §15.15).
In addition, a local variable whose declaration lacks an initializer
is effectively final if all of the following are true:
It is not declared final.
Whenever it occurs as the left-hand operand of an assignment
operator, it is definitely unassigned and not definitely assigned
before the assignment; that is, it is definitely unassigned and not
definitely assigned after the right-hand operand of the assignment
(§16 (Definite Assignment)).
It never occurs as the operand of a prefix or postfix increment or
decrement operator.
If a variable is effectively final, adding the final modifier to its
declaration will not introduce any compile-time errors. Conversely, a
local variable or parameter that is declared final in a valid program
becomes effectively final if the final modifier is removed.
It's still the same rule, except the compiler doesn't force you to explicitely define the variable as final anymore. If it is effectively final, you can access it. If it's not (i.e. the compiler detects that the variable is reassigned), then it doesn't compile.
Related
I was reading the question here : Java : in what order are static final fields initialized?
According to the answer
"except that final class variables and fields of interfaces whose
values are compile-time constants are initialized first ..."
I think this is not correct because the following will fail :
static {
String y = x;
}
public static final String x = "test";
In the static block, x is not recognized. Can anyone please comment if that answer is correct ?
The order of initialization doesn't change the fact that the JLS doesn't let you refer to variables before they're declared in various cases. This is described in JLS§8.3.3:
Use of class variables whose declarations appear textually after the use is sometimes restricted, even though these class variables are in scope (§6.3). Specifically, it is a compile-time error if all of the following are true:
The declaration of a class variable in a class or interface C appears textually after a use of the class variable;
The use is a simple name in either a class variable initializer of C or a static initializer of C;
The use is not on the left hand side of an assignment;
C is the innermost class or interface enclosing the use.
That's why your code gets this compilation erorr:
error: illegal forward reference
The statement that static fields that are constant variables are initialized first is indeed defined in JLS§12.4.2:
Otherwise, record the fact that initialization of the Class object for C is in progress by the current thread, and release LC.
Then, initialize the static fields of C which are constant variables (§4.12.4, §8.3.2, §9.3.1).
...
Next, execute either the class variable initializers and static initializers of the class, or the field initializers of the interface, in textual order, as though they were a single block.
As you can see, constant variables are initialized in step 6, but others in step 9.
This demonstrates the behavior:
public class Example {
static String y;
static {
y = foo();
}
static String foo() {
return x.toUpperCase();
}
public static final String x = "test";
public static void main(String[] args) throws Exception {
System.out.println(x);
System.out.println(y);
}
}
That compiles, and outputs:
test
TEST
In contast, if you change the x line so it's not constant anymore:
public static final String x = Math.random() < 0.5 ? "test" : "ing";
It compiles, but then fails because x is null as of y = foo();.
For the avoidance of doubt: I don't recommend using methods to initialize fields like that. :-)
As this answer states:
... they are initialized in the order in which they appear in the source.
You are absolutely right, your example fails, because you are trying to use a variable that is declared after the usage. Since static block are executed in order of the source code, you are absolutely correct and should suggest and edit for that answer, because this statement is invalid:
except that final class variables ... are initialized first.
I've checked Forward References During Field Initialization and this the answer from #assylias, but still I got no answer to the why.
Why a static block can assign the static variable declared after it but can NOT access it?
class Parent {
static {
i = 2; // valid
// can only assign new value to it instead of accessing it?
// System.out.println(i); // invalid - compile-error
}
static int i = 0;
static {
i = 3; // valid
}
}
Is it due to the fact: the value is not initialized yet, so we just explicitly inhibit you from using it? or there are something related to security I don't know?
updated
this is not a duplicate of that problem which is about
Why this doesn't happen when accessing with class name?
This question is about why we have this design? for what purpose?
Static fields are initialized based on the order they appear in the code.
So, when you assign a value to i variable you just say to the compiler: "Hey, guy, when you get to initialize this variable, set its value to...". But you can not use it until it's initialized because it simply does not exist yet.
UPDATE:
As it says in the book "The Java Language Specification" by James Gosling, Bill Joy, Guy Steele and Gilad Bracha:
These restrictions are designed to catch, at compile time, circular or
otherwise malformed initializations.
Consider this:
static {
i = 2;
j = i + 5; //should it be 7 or 15?
}
static int i = 10;
static int j;
Should j variable be 7 or 15?
If it's 7, then we have initialized i variable twice, which is not possible, since the field is static. If it's 15 then what does i = 2; mean?
This code is ambiguous, so Java Specification does not allow to do that.
After some further reading, I think Pavel is not quite accurate in this point as #Holger pointed out in the comment.
we have initialized i variable twice, which is not possible, since the field is static.
As the 12.4.2. Detailed Initialization Procedure points out
For each class or interface C, there is a unique initialization lock LC. The mapping from C to LC is left to the discretion of the Java Virtual Machine implementation.
I suppose the initialization twice is okay to the class initializer itself as long as it's just once to the invoking clients.
But the demos Pavel provided still stands its position so I basically just reuse it here but with different explanation.
static {
i = 2;
j = i + 5;
// no one knows whether "i" here initialized properly here
}
static int i = 10;
static int j;
But when you use MyClass.i directly in j = MyClass.i + 5, the compiler would know it would then be okay as 8.3.3. Forward References During Field Initialization detailed with four conditions.
Specifically, it is a compile-time error if all of the following are
true:
The declaration of a class variable in a class or interface C appears
textually after a use of the class variable;
The use is a simple name in either a class variable initializer of C
or a static initializer of C;
The use is not on the left hand side of an assignment;
C is the innermost class or interface enclosing the use.
And there is a detailed discussion in this answer already.
To wind it up, I think this would be for predictable behavior to add these restrictions. Once again, the other official purpose pointed out in 8.3.3. Forward References During Field Initialization.
These restrictions are designed to catch, at compile time, circular or otherwise malformed initializations.
The same is also applicable to the non-static members. You can assign values to them in instance initializer block but can not use them before initialization.
class Parent {
{
x = 3; // works fine
// System.out.println(x); // gives compilation error.
}
int x = 0;
public static void main(String[] args) {
}
}
This question already has answers here:
Why Final variable doesn't require initialization in main method in java?
(4 answers)
Closed 4 years ago.
As per my knowledge, final variables must/can be initialized only once otherwise compiler is supposed to throw an error.
If the final instance variable x is not initialized an error is thrown but I faced no error when the local variable y is kept uninitialized in the following code:
import java.util.*;
public class test
{
final int x = 5;// if final variable x uninitialized, compilation error occurs
public static void main(String[] args)
{
final int y; // y is not initialized, **no error is thrown**
System.out.println("test program");
}
}
The local variable isn't used and therefore can be left uninitialized
You will get compile error when try to use it (even if it's not final):
System.out.println("test program" + y);
The local variable y may not have been initialized
The Java Language Specification does not state that a final variable must be assigned (emphasis mine):
A final variable may only be assigned to once.
And:
A blank final is a final variable whose declaration lacks an initializer.
So your y variable is a blank final, and since it's not referenced anywhere further in your code, it's perfectly fine to leave it unassigned.
import java.util.*;
public class test
{
final int x = 5;
public static void main(String[] args)
{
final int y;
System.out.println("test program");
y=6;
y=7;
}
}
y=7 will give error:The final local variable y may already have been assigned. Since it is a final variable, and it has been assigned to 6.
IMHO, a final local variable means once assigned, it cannot be re-assigned. But by final int y you are only declaring a final variable without assignment(initialization), which is legal in Java.(But in order to use it you still have to initialize it, or an error occurs.)
Update:
As commented below, you have noticed the difference between a class field final variable and a local final variable.
From Java Language Specification:
a final field must be definely assigned in the static initializer or the constructor:
8.3.1.2 final Fields
A field can be declared final (§4.12.4). Both class and instance variables (static
and non-static fields) may be declared final.
A blank final class variable must be definitely assigned by a static initializer of
the class in which it is declared, or a compile-time error occurs (§8.7, §16.8).
A blank final instance variable must be definitely assigned at the end of every
constructor of the class in which it is declared, or a compile-time error occurs (§8.8,
§16.9).
(Note that a non-final field can be left un-initialized)
2.A local variable(whether final or not) must be explicitly given a value before it is used:(chapter 4.12.5,P88)
• A local variable (§14.4, §14.14) 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 using the rules for definite assignment (§16 (Definite Assignment)).
As per the definition of a final variable, they can be initialized only once. In your code, you haven't initialized 'y' and you're not using it anywhere as well.
But if you do the following,
final int y;
System.out.println(y);
you will get 'variable y might not have been initialized'
You will get an error when you try to use the declared final variable.
System.out.println(y);
I have a program like this:
class Test {
final int x;
{
printX();
}
Test() {
System.out.println("const called");
}
void printX() {
System.out.println("Here x is " + x);
}
public static void main(String[] args) {
Test t = new Test();
}
}
If I try to execute it, i am getting compiler error as : variable x might not have been initialized based on java default values i should get the below output right??
"Here x is 0".
Will final variables have dafault values?
if I change my code like this,
class Test {
final int x;
{
printX();
x = 7;
printX();
}
Test() {
System.out.println("const called");
}
void printX() {
System.out.println("Here x is " + x);
}
public static void main(String[] args) {
Test t = new Test();
}
}
I am getting output as:
Here x is 0
Here x is 7
const called
Can anyone please explain this behavior..
http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html, chapter "Initializing Instance Members":
The Java compiler copies initializer blocks into every constructor.
That is to say:
{
printX();
}
Test() {
System.out.println("const called");
}
behaves exactly like:
Test() {
printX();
System.out.println("const called");
}
As you can thus see, once an instance has been created, the final field has not been definitely assigned, while (from http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.2):
A blank final instance variable must be definitely assigned at
the end of every constructor of the class in which it is
declared; otherwise a compile-time error occurs.
While it does not seem to be stated explitely in the docs (at least I have not been able to find it), a final field must temporary take its default value before the end of the constructor, so that it has a predictable value if you read it before its assignment.
Default values: http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5
On your second snippet, x is initialized on instance creation, so the compiler does not complain:
Test() {
printX();
x = 7;
printX();
System.out.println("const called");
}
Also note that the following approach doesn't work. Using default value of final variable is only allowed through a method.
Test() {
System.out.println("Here x is " + x); // Compile time error : variable 'x' might not be initialized
x = 7;
System.out.println("Here x is " + x);
System.out.println("const called");
}
JLS is saying that you must assign the default value to blank final instance variable in constructor (or in initialization block which is pretty the same). That is why you get the error in the first case. However it doesn't say that you can not access it in constructor before. Looks weird a little bit, but you can access it before assignment and see default value for int - 0.
UPD. As mentioned by #I4mpi, JLS defines the rule that each value should be definitely assigned before any access:
Each local variable (§14.4) and every blank final field (§4.12.4, §8.3.1.2) must have a definitely assigned value when any access of its value occurs.
However, it also has an interesting rule in regards to constructors and fields:
If C has at least one instance initializer or instance variable initializer then V is [un]assigned after an explicit or implicit superclass constructor invocation if V is [un]assigned after the rightmost instance initializer or instance variable initializer of C.
So in second case the value x is definitely assigned at the beginning of the constructor, because it contains the assignment at the end of it.
If you don't initialize x you'll get a compile-time error since x is never initialized.
Declaring x as final means that it can be initialized only in the constructor or in initializer-block (since this block will be copied by the compiler into every constructor).
The reason that you get 0 printed out before the variable is initialized is due to the behavior defined in the manual (see: "Default Values" section):
Default Values
It's not always necessary to assign a value when a field is declared.
Fields that are declared but not initialized will be set to a
reasonable default by the compiler. Generally speaking, this default
will be zero or null, depending on the data type. Relying on such
default values, however, is generally considered bad programming
style.
The following chart summarizes the default values for the above data
types.
Data Type Default Value (for fields)
--------------------------------------
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char '\u0000'
String (or any object) null
boolean false
The first error is the compiler complaining that you have a final field, but no code to initialize it - simple enough.
In the second example, you have code to assign it a value, but the sequence of execution means you reference the field both before and after assigning it.
The pre-assigned value of any field is the default value.
All non-final fields of a class initialize to a default value (0 for nummeric data types, false for boolean and null for reference types, sometimes called complex objects). These fields initialize before a constructor (or instance initialization block) executes independent of whether or not the fields was declared before or after the constructor.
Final fields of a class has no default value and must be explicitly initialized just once before a class constructor has finished his job.
Local variables on the inside of an execution block (for example, a method) has no default value. These fields must be explicitly initialized before their first use and it doesn't matter whether or not the local variable is marked as final.
Let me put it in the simplest words I can.
final variables need to be initialized, this is mandated by the Language Specification.
Having said that, please note that it is not necessary to initialize it at the time of declaration.
It is required to initialize that before the object is initialized.
We can use initializer blocks to initialize the final variables. Now, initializer blocks are of two types
static and non-static
The block you used is a non-static initializer block. So, when you create an object, Runtime will invoke constructor and which in turn will invoke the constructor of the parent class.
After that, it will invoke all the initializers (in your case the non-static initializer).
In your question, case 1: Even after the completion of initializer block the final variable remains un-initialized, which is an error compiler will detect.
In case 2: The initializer will initialize the final variable, hence the compiler knows that before the object is initialized, the final is already initialized. Hence, it will not complain.
Now the question is, why does x takes a zero. The reason here is that compiler already knows that there is no error and so upon invocation of init method all the finals will be initialized to defaults, and a flag set that they can change upon actual assignment statement similar to x=7.
See the init invocation below:
As far as I'm aware, the compiler will always initialize class variables to default values (even final variables). For example, if you were to initialize an int to itself, the int would be set to its default of 0. See below:
class Test {
final int x;
{
printX();
x = this.x;
printX();
}
Test() {
System.out.println("const called");
}
void printX() {
System.out.println("Here x is " + x);
}
public static void main(String[] args) {
Test t = new Test();
}
}
The above would print the following:
Here x is 0
Here x is 0
const called
If I try to execute it, i am getting compiler error as : variable x might not have been initialized based on java default values i should get the below output right??
"Here x is 0".
No. You are not seeing that output because you are getting a compile-time error in the first place. Final variables do get a default value, but the Java Language Specification (JLS) requires you to initialize them by the end of the constructor (LE: I'm including initialization blocks here), otherwise you'll get a compile-time error which will prevent your code to be compiled and executed.
Your second example respects the requirement, that's why (1) your code compiles and (2) you get the expected behaviour.
In the future try to familiarize yourself with the JLS. There's no better source of information about the Java language.
It seems intuitively clear that in Java, instance variable intitializers are executed in the order in which they appear in the class declaration.
This certainly appears to be the case in the JDK I am using. For example, the following:
public class Clazz {
int x = 42;
int y = this.z;
int z = this.x;
void print() {
System.out.printf("%d %d %d\n", x, y, z);
}
public static void main(String[] args) {
new Clazz().print();
}
}
prints 42 0 42 (in other words, y picks up the default value of z).
Is this ordering actually guaranteed? I've been looking through the JLS, and can't find any explicit confirmation.
Yes, it is.
The se7 JLS covers instance variable initialization order in the 12.5 Execution section:
...
4. Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable
initializers to the corresponding instance variables, in the
left-to-right order in which they appear textually in the source code
for the class. If execution of any of these initializers results in an
exception, then no further initializers are processed and this
procedure completes abruptly with that same exception. Otherwise,
continue with step 5.
...
the JLS for Java 5 mentions in the "Classes" section:
The static initializers and class variable initializers are executed
in textual order.
yes the variables initialisation in the class are executed in the same order. So in your second line y takes the default value o of z since z is not initialised