Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variable might not have been initialized, but it's set in constructor

Tags:

java

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".

like image 696
Tomáš Tököly Avatar asked Jan 18 '18 07:01

Tomáš Tököly


People also ask

Can we initialize variable in constructor?

A constructor is typically used to initialize instance variables representing the main properties of the created object. If we don't supply a constructor explicitly, the compiler will create a default constructor which has no arguments and just allocates memory for the object.

Do you have to initialize all variables in constructor?

in short.. it's fine to not initialize your member variables in the constructor as long as you initialize them somewhere in the class before using them.. Save this answer.


4 Answers

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.

like image 185
QBrute Avatar answered Nov 05 '22 05:11

QBrute


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.

like image 40
jeet427 Avatar answered Nov 05 '22 06:11

jeet427


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

like image 33
gtosto Avatar answered Nov 05 '22 05:11

gtosto


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.

like image 1
Eugene Avatar answered Nov 05 '22 06:11

Eugene