9

Today I'm facing a strange behavior which I couldn't figure out why.

Imagine we have a final variable in a typical class in Java. We can initialize it instantly or in class constructor like this:

public class MyClass {

    private final int foo;

    public MyClass() {
        foo = 0;
    }
}

But I don't know why we can't call a method in constructor and initialize foo in that method, like this:

public class MyClass {

    private final int foo;

    public MyClass() {
        bar();
    }

    void bar(){
        foo = 0;
    }
}

Because I think we are still in constructor flow and it doesn't finished yet. Any hint will be warmly appreciated.

Alireza
  • 1,018
  • 1
  • 8
  • 23
  • 17
    Because there is nothing that stops your `bar()` method from getting called more than once. And final variables can only be assigned once. – OH GOD SPIDERS Mar 12 '18 at 13:21
  • 5
    Not to mention you could extend `MyClass` and override `bar()`. Then it wouldn't be assigned at all and we can't have that either. – Kayaman Mar 12 '18 at 13:22
  • @OHGODSPIDERS thanks a lot, yes I think this is the reason and I would accept the answer if it was't comment – Alireza Mar 12 '18 at 13:28
  • The reason is simple :- You can call the bar method again which will try to reassign a value to a final member. You can try making foo as static final, compiler won't allow you initialize it constructor itself. As constructor get's called every time you create instance of the class but static final members can't be reassigned even through different instances. – Srijan Mehrotra Mar 12 '18 at 13:30
  • @Echtniet No they are different because the constructor has not finished yet – Alireza Mar 12 '18 at 13:41

3 Answers3

11

First, assigning the value at declaration time is copied into every constructor for you by the compiler. Second, you can use a method to initialize the value, but you need to return it for that to work. As others' note, you are required to ensure this value is set once.

public class MyClass {
    private final int foo = bar();

    private static int bar() {
        return 0;
    }
}

Which is equivalent to

public class MyClass {
    private final int foo;

    public MyClass() {
        this.foo = bar();
    }

    private static int bar() {
        return 0;
    }
}

Note that bar is static, because otherwise you need an instance to call it.

Elliott Frisch
  • 198,278
  • 20
  • 158
  • 249
  • "assigning the value at declaration time is copied" is not exactly right. The in-body assignment to `final` members happens before any constructors, including `super()`. This matters when the overridden methods are called from a parent constructor (a poor design, but it happens). –  Mar 12 '18 at 14:09
3

You can only initialize a final variable once. There are three forms of final variables:

  • class final variables
  • Instance final variables
  • Local final variables.

For class final variables, the variables can be initialized in either the declaration or static initializer:

class Program {
static final int i1 = 10;
static final int i2;
static {
    i2 = 10;
}

}

For instance final variables, the variables can be initialized in the declaration, instance initializer, or constructor:

class Program {
final int i1 = 10;
final int i2;
final int i3;
{
    i2 = 10;
}

Program() {
    i3 = 10;
}

}

For local final variables, the variables can be initlialized in the declaration or any place after its declaration. The local final variables must be initialized before they are used.

class Program {
void method() {
     final int i1 = 10;
     final int i2;
     System.out.println(i1);
     i2 = 10;
     System.out.println(i2);
     return ;
}

}

Source: Refer link Reference Link

Subash J
  • 2,028
  • 3
  • 13
  • 27
1

Final modifier on field (or variable) means that compiler will ensure that both of the following are true:

  • The field is initialized at least once during object construction, unless object construction fails.
  • The field is initialized at most once.

For your code, none of those is guaranteed:

  • Some subclass might override method bar.
  • Some other class in the same package might call method bar once more.

It might be tempting to use private method rather than package-private. While it could guarantee you both of those conditions (unless you try to break it by reflection), javac still will not accept it, because it is not so powerful. There are some good reasons:

  1. First, it has to have some limits. If the compiler was able to fully decide if both conditions are satisfied, it would be able to decide halting problem, which is not possible. So, some reasonable subset was chosen.
  2. Imagine it is able to detect this particular situation. This means you have some private method that has to be called from constructor and not from elsewhere. In such cases, programmer would need a descriptive error message why such a private method (that looks like a normal method at first sight) cannot be called here. Later, someone would create some monster method that conditionally assigns to the final field. For some complex-enough conditions, javac would be unable to find out if it assigns to the final field or not, so someone would face some mysterious error message. I don't think it is easy to make a good error message in such situation.
  3. Calling private instance methods is already tricky. As the method will operate on a not-fully-initialized object, so it can, for example, read some uninitialized (even final) properties.
  4. I believe that constructor should be rather short and simple, usually just assigning parameters to fields, plus some validation. If things are getting complex, you might want to create a factory method. Object creation would become clearly separated from object methods, which is not the case when you need to call a private method.

Instead of such mouse-cat-hunt, the language designers have decided to support just some well understood cases. In other cases, the code can be probably refactored. So, language creators can focus on some more important aspects.

v6ak
  • 1,636
  • 2
  • 12
  • 27