How does the "final" keyword in Java work? (I can still modify an object.)

530,231

Solution 1

You are always allowed to initialize a final variable. The compiler makes sure that you can do it only once.

Note that calling methods on an object stored in a final variable has nothing to do with the semantics of final. In other words: final is only about the reference itself, and not about the contents of the referenced object.

Java has no concept of object immutability; this is achieved by carefully designing the object, and is a far-from-trivial endeavor.

Solution 2

This is a favorite interview question. With this questions, the interviewer tries to find out how well you understand the behavior of objects with respect to constructors, methods, class variables (static variables) and instance variables.

import java.util.ArrayList;
import java.util.List;

class Test {
    private final List foo;

    public Test() {
        foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }

    public void setFoo(List foo) {
       //this.foo = foo; Results in compile time error.
    }
}

In the above case, we have defined a constructor for 'Test' and gave it a 'setFoo' method.

About constructor: Constructor can be invoked only one time per object creation by using the new keyword. You cannot invoke constructor multiple times, because constructor are not designed to do so.

About method: A method can be invoked as many times as you want (Even never) and the compiler knows it.

Scenario 1

private final List foo;  // 1

foo is an instance variable. When we create Test class object then the instance variable foo, will be copied inside the object of Test class. If we assign foo inside the constructor, then the compiler knows that the constructor will be invoked only once, so there is no problem assigning it inside the constructor.

If we assign foo inside a method, the compiler knows that a method can be called multiple times, which means the value will have to be changed multiple times, which is not allowed for a final variable. So the compiler decides constructor is good choice! You can assign a value to a final variable only one time.

Scenario 2

private static final List foo = new ArrayList();

foo is now a static variable. When we create an instance of Test class, foo will not be copied to the object because foo is static. Now foo is not an independent property of each object. This is a property of Test class. But foo can be seen by multiple objects and if every object which is created by using the new keyword which will ultimately invoke the Test constructor which changes the value at the time of multiple object creation (Remember static foo is not copied in every object, but is shared between multiple objects.)

Scenario 3

t.foo.add("bar"); // Modification-2

Above Modification-2 is from your question. In the above case, you are not changing the first referenced object, but you are adding content inside foo which is allowed. Compiler complains if you try to assign a new ArrayList() to the foo reference variable.
Rule If you have initialized a final variable, then you cannot change it to refer to a different object. (In this case ArrayList)

final classes cannot be subclassed
final methods cannot be overridden. (This method is in superclass)
final methods can override. (Read this in grammatical way. This method is in a subclass)

Solution 3

Final keyword has a numerous way to use:

  • A final class cannot be subclassed.
  • A final method cannot be overridden by subclasses
  • A final variable can only be initialized once

Other usage:

  • When an anonymous inner class is defined within the body of a method, all variables declared final in the scope of that method are accessible from within the inner class

A static class variable will exist from the start of the JVM, and should be initialized in the class. The error message won't appear if you do this.

Solution 4

The final keyword can be interpreted in two different ways depending on what it's used on:

Value types: For ints, doubles etc, it will ensure that the value cannot change,

Reference types: For references to objects, final ensures that the reference will never change, meaning that it will always refer to the same object. It makes no guarantees whatsoever about the values inside the object being referred to staying the same.

As such, final List<Whatever> foo; ensures that foo always refers to the same list, but the contents of said list may change over time.

Solution 5

If you make foo static, you must initialize it in the class constructor (or inline where you define it) like the following examples.

Class constructor (not instance):

private static final List foo;

static
{
   foo = new ArrayList();
}

Inline:

private static final List foo = new ArrayList();

The problem here is not how the final modifier works, but rather how the static modifier works.

The final modifier enforces an initialization of your reference by the time the call to your constructor completes (i.e. you must initialize it in the constructor).

When you initialize an attribute in-line, it gets initialized before the code you have defined for the constructor is run, so you get the following outcomes:

  • if foo is static, foo = new ArrayList() will be executed before the static{} constructor you have defined for your class is executed
  • if foo is not static, foo = new ArrayList() will be executed before your constructor is run

When you do not initilize an attribute in-line, the final modifier enforces that you initialize it and that you must do so in the constructor. If you also have a static modifier, the constructor you will have to initialize the attribute in is the class' initialization block : static{}.

The error you get in your code is from the fact that static{} is run when the class is loaded, before the time you instantiate an object of that class. Thus, you will have not initialized foo when the class is created.

Think of the static{} block as a constructor for an object of type Class. This is where you must do the initialization of your static final class attributes (if not done inline).

Side note:

The final modifier assures const-ness only for primitive types and references.

When you declare a final object, what you get is a final reference to that object, but the object itself is not constant.

What you are really achieving when declaring a final attribute is that, once you declare an object for your specific purpose (like the final List that you have declared), that and only that object will be used for that purpose: you will not be able to change List foo to another List, but you can still alter your List by adding/removing items (the List you are using will be the same, only with its contents altered).

Share:
530,231
G.S
Author by

G.S

Software Development Manager - java, spring/springboot, AWS, Microservices

Updated on July 08, 2022

Comments

  • G.S
    G.S almost 2 years

    In Java we use final keyword with variables to specify its values are not to be changed. But I see that you can change the value in the constructor / methods of the class. Again, if the variable is static then it is a compilation error.

    Here is the code:

    import java.util.ArrayList;
    import java.util.List;
    
    class Test {
      private final List foo;
    
      public Test()
      {
          foo = new ArrayList();
          foo.add("foo"); // Modification-1
      }
      public static void main(String[] args) 
      {
          Test t = new Test();
          t.foo.add("bar"); // Modification-2
          System.out.println("print - " + t.foo);
      }
    }
    

    Above code works fine and no errors.

    Now change the variable as static:

    private static final List foo;
    

    Now it is a compilation error. How does this final really work?