Reputation: 83
so I've tried to search for answer, but for this error message I'm founding irelevant answers to my problem.
Here it is. Why this code:
Case 1)
public class A {
private final String A;
private final String B;
private final String C = A + B;
public A(String A, String B) {
this.A = A;
this.B = B;
}
}
for line private final String C = A + B;
it says these errors:
java: variable A might not have been initialized
java: variable B might not have been initialized
But this works like a charm:
Case2)
public class K {
private final String K;
private final String L;
private final String M = kPlusL();
public K(final String K, final String L) {
this.K = K;
this.L = L;
}
private String kPlusL() {
return K + L;
}
}
Or also this works like a charm:
Case 3)
public class O {
protected final String O;
protected final String P;
public O(final String O, final String P) {
this.O = O;
this.P = P;
}
}
public class Q extends O {
private final String Q = O + P;
Q (final String O, final String P) {
super(O, P);
}
}
Can somebody explain me why please? I'm using IntelliJ IDEA and Java 1.8.0_151.
All three cases are doing exact same thing (puts two Strings together), but one is doing it directly and second and third "indirectly".
Upvotes: 8
Views: 12674
Reputation: 120848
The JLS parts are relevant indeed (since you already got them, not going to link that again), the byte code is a little bit more interesting.
For the first example (slightly modified):
private String A;
private String B;
private final String C = A + B;
public FirstExample(String A, String B) {
this.A = A;
this.B = B;
}
System.out.println(new FirstExample("a", "b").C);
This will print nullnull
and it makes sense if you look at the generated byte code (only the relevant parts). This btw is correct according to JLS, as instance fields are initialized before the rest of the constructor body.
getfield // Field A:Ljava/lang/String;
getfield // Field B:Ljava/lang/String;
// this is just concat the two Strings with java-9
invokedynamic // InvokeDynamic #0:makeConcatWithConstants
....
putfield // Field C:Ljava/lang/String;
putfield // Field A:Ljava/lang/String;
putfield // Field B:Ljava/lang/String;
The point to get is that instance variables are initialized before the rest of the constructor (basically C
before A
and B
). It makes sense now why adding final
to A
and B
would not compile.
The second example is a bit more interesting. These are called Forward references and are allowed by the spec (actually it is correct to say they are not disallowed) For example, this:
String x = y;
String y = "a";
is not allowed, but this on the other hand is:
String x = getIt();
String y = "a";
public String getIt() {
return y;
}
The downside is that y
will be initialized with null
and not a
first time; thus x
is going to be null.
The last example is yet again in conformation with JLS
. The super constructor runs first, thus initializing those fields; only after that the Q
variable is initialized via the inherited (already with values from constructor) fields, thus producing the expected value.
Upvotes: 1
Reputation: 2305
As the first two cases are pretty much clear and answered, I would like to focus on the third case where you have inheritance. According to the initialization sequence is specified in JLS:
Just before a reference to the newly created object is returned as the result, the indicated constructor is processed to initialize the new object using the following procedure:
- Assign the arguments for the constructor to newly created parameter variables for this constructor invocation.
If this constructor begins with an explicit constructor invocation (§8.8.7.1) of another constructor in the same class (using this), then evaluate the arguments and process that constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason; otherwise, continue with step 5.
This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). Evaluate the arguments and process that superclass constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, continue with step 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.
- Execute the rest of the body of this constructor. If that execution completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, this procedure completes normally.
Example 12.5-1. Evaluation of Instance Creation given in the link clearly explains the initialization order.
So, as per your third example(look at points 3 and 4), the execution order would be:
O
(since you did not specify any intializers, they will be initialized to null)O
constructor -- public O(final String O, final String P)
which assigns O
and P
with the given valuesQ
-- private final String Q = O + P;
which contatenates O and P and assigns the result to QQ
constructor -- public Q(final String O, final String P)
ends
So, by the completion of class Q
constructor, the object has been initialized properly.Upvotes: 0
Reputation: 417
Because First, it Defines A and B, and Defines and Initialize C with the value of A and B, And then goes to the constructor and initialize A and B. So as you can see there are no values for A and B Until the calling of the constructor. Look at the code below:
public class A {
private final String A; // = Null
private final String B; // = Null
private final String C = A + B; // Error Cause There is no value for A and B
public A(String A, String B) {
this.A = A;
this.B = B;
}
}
Just Place C = A + B
inside the constructor.
Upvotes: 0
Reputation: 596
In case 2 you are setting value of M
using kPlusL()
which will cast the null into string during concatenation. Hence it will have value as "nullnull".
In case 3 you are inheriting a class so super class constructor will be invoked before child class instantiation. Hence it will have the values for O
and P
to be assigned in Q
.
Upvotes: 2
Reputation: 1341
Apart from the first case which the compiler complains to, the other two cases are easily explained by the java object initialization detail.
I suggest you read this article, you will find all of your answers.
https://www.javaworld.com/article/2076614/core-java/object-initialization-in-java.html?page=2
Upvotes: 1
Reputation: 4536
When you try to initialize C
in your first case, A
and B
are still uninitialized at this point, so private final String C = A + B;
will fail.
Try initializing C
inside of your constructor:
public class A {
private final String A;
private final String B;
private final String C;
public A(String A, String B) {
this.A = A;
this.B = B;
this.C = A + B;
}
}
Here's the relevant bit from the JLS
For every access of a local variable or blank final field x, x must be definitely assigned before the access, or a compile-time error occurs.
Upvotes: 4