What are the options to initialize a final variable in a Java class?

Asked

Viewed 319 times

2

Given the class below, what are the boot options for the variable ANGLE, since final can be initialized only once?

class Triangle {
    public int base;
    public int height;
    private final double ANGLE;
}

1 answer

7


The best way is to use a builder for this:

class Triangle {
    public int base;
    public int height;
    private final double angle;

    public Triangle(double angle) {
        this.angle = angle;
    }
}

Other less interesting ways would be:

  • Initialize with direct assignment:

    private final double angle = 60.0;
    

    But this shape is not very interesting, because in this case you could put the modifier static in it, since all instances will receive the same value. However, it still gives to make each instance receive a different value:

    private final double angle = Math.random() * 180.0;
    

    A more complex example:

    private static int instanciasCriadas = 0;
    private final numeroDestaInstancia = ++instanciasCriadas;
    
  • Using an instance initializer:

    private final double angle;
    
    {
        angle = Math.random() * 180.0;
    }
    

However, the use of the constructor ends up being the cleanest and most versatile way to do this. In particular, using instance initializers is something that occurs very rarely in practice. The only two advantages that the instance boot block can have over the other forms I envision are:

  1. A very specific case consisting of a combination of factors where: (a) there are a lot of different builders; (b) the assignment logic of at least one of the fields is the same independent of the constructor used and does not depend on any of the constructor parameters or anything that runs within it and (c) the steps to initialize are not things that can be represented only with a simple expression.

  2. Initializing anonymous class fields that have superclasses, when it needs to use some methods on its initialization.

Both cases are very special, representing code Smells and it is always possible to remember things to avoid them.

Note that I put angle in lower-case, not upper-case. The reason is that to be considered a constant and then to be written in capital letters, the convention says that the variable has to be static final, and not just final (there are other rules besides that, but it starts there).

Finally, the compiler will move underneath the cloths, all direct assignments to fields will not static, as well as the instance initialization blocks, into the constructor. After that, it will scan the resulting constructor code to make sure that all fields final receive a value until the completion of the constructor’s execution and only receive it once, giving a compilation error otherwise (more than one assignment or possible non-attribution). This check made by the compiler almost always ensures that these fields can only be used when properly initialized.

Why almost always? Why is there a way to drill this by invoking methods that manipulate these fields from within the constructor or the instance initializer block before they have been initialized:

class Ruim {

    private final int sempreTres;

    public Ruim() {
        mostra();
        sempreTres = 3;
    }

    public void mostra() {
        System.out.println(sempreTres);
    }

    public static void main(String[] args) {
        Ruim r = new Ruim();
        r.mostra();
    }
}

This is the way out that this generates:

0
3

See here in ideone.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.