Java immutability when defining members in a function called by constructor

153 Views Asked by At

I have class like this:

abstract class Parent {
  protected Parent(Raw rawData) {
     deserialize(rawData);
  }
  protected abstract void deserialize(Raw rawData);
}

class Child extends Parent {
   final byte firstByte;
   public Child(Raw rawdData) { super(rawData); }
   protected void deserialize(Raw rawData) { 
     firstByte = rawData.getFirst();
   }
}

So basically any child class that extends Parent will define a deserialize() to deserialize the rawData to fill in their member variables, and in their constructor they will just do super(rawData). The deserialize function of the child class will automatically execute.

Error message:

Child.java:5: error: cannot assign a value to final variable firstByte
 firstByte = rawData.getFirst();

However doing this Java does not allow me to define member variables as final which is weird because I'm doing initialization in the constructor. What is the best way to do the same functionality but allowing member to be final ?

4

There are 4 best solutions below

0
On BEST ANSWER

A final variable can be assigned a value only once. The compiler doesn't check whether a method is called only once; hence, final variables cannot be assigned in methods. (In case of the deserialize method the compiler couldn't determine if the method is called only once, even if it would want to. The method is protected and there is nothing which prevents a subclass from calling it twice.) Thus, you can only assign to final member variables in the constructor (or initialize them immediately when you define them).

The solution is to make the variable not final. You can still ensure that instances of your class are immutable by restricting access to the variable (make it private) and only provide a getter for it (if you needs its value outside of the class).

Note also that it is better not to call methods from a constructor, especially methods which can be overridden by subclasses. Although it will probably work now if you not depend on the state of the object, you have to realize that when the method is called, the object is not completely constructed yet. If you (or someone else) want to modify that method in the future, you may have forgotten this and introduce hard-to-find bugs. For example, the following little program:

class Bar {
    public Bar() {
        f();
    }    
    protected void f() { }
}

public class Foo extends Bar  {
    final String a;
    public Foo() {
        a = "Hello";
    }
    protected void f() {
        System.out.println(a);
    }
    public static void main(String... args) {
        Foo foo = new Foo();
        System.out.println(foo.a);
    }
}

outputs

null
Hello

although the same final string reference is printed two times.

So yes, you should call your deserialize method in each of the subclasses' constructor instead of in the superclass constructor, so that each constructor only constructs the part of the object it knows about.

5
On

When you call a super constructor, the "super" part of the object is being constructed, while the "sub" part of the object is a complete waste land; you may not read from or write ot it; actually the "sub" part may not even exist conceptually.

A super constructor calling an abstract method is a very bad idea, because it may depend on the subclass state, which does not exist yet.

2
On

As far as the compilation error, you can't delegate the assignment of the final to another method. You must do the assignment in the constructor or inline with the declaration.

    public Child(Raw rawdData) { 
        super(rawData);
        firstByte = rawData.getFirst();
    }

As bayou.io states, calling an abstract method from a constructor is a really, really bad idea.

0
On

Even though you are calling deserialize from the constructor. You cannot really enforce that deserialize is only called from the constructor.

The java compiler knows this, and this is why it's complaining. Final member variables can only be assigned at initialization or within the constructor.

Hope this helps.