Final Keyword in Java
In this article, we will go through what the final keyword does in Java, and when and how you should use it.
1. What Does final Mean in Java
The final keyword in Java has a different meaning depending on where you apply it:
- When it comes to a variable, it means that you cannot change the value if it is a primitive, and you cannot change the reference if it is an object.
- When it comes to a class, it means that this class cannot be extended by other classes, or in other words, this class cannot have descendants.
- When it comes to a method, it means that this method cannot be overridden by other methods.
2. Using the final Keyword with a variable.
In this section, we’ll see how the final keyword works when it is used with a variable.
2.1 Using final Keyword With a Primitive
When it comes to primitives, you can only change the value of it, so consider the example below:
final int x = 5; x = 6;
As you should have guessed, the snippet above will not compile at all and will produce this error:
java: cannot assign a value to final variable x
Note that in this case as well as in the next case, you can also split declaration and initialization as shown below:
final int x; x = 6;
2.2 Using the final Keyword With an Object
You can also try it with any object, e.g. a List, but the only limitation is that the reference should never be changed. This means that you can add, remove or even empty the list without any problem:
final List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.remove("hello");
list.clear();
but if you try to change the reference as shown below:
list = new ArrayList<>();
You will get the same error:
java: cannot assign a value to final variable list
2.3 Using the final Keyword With an Instance Variable
When it comes to applying the final keyword to an instance variable, you have these two options:
- Set a value to the variable or the object when declaring the variable.
- Leave the variable without a value, which will make the initialization inside a constructor mandatory.
The following would produce the same error as in the previous sections:
public class Person {
private final String name;
}
So if we apply the 1st option, it would be something like this:
public class Person {
private final String name;
}
Of course, this limits us as when we create a new object, it would always have the same value.
If we choose option 2, we would have the following:
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
}
This way allows us to create a new immutable object with any name we might like.
2.4 Using the final Keyword With a Parameter of a Method
You can pass a parameter to a method as final if you want to ensure that the value of this variable will not be altered inside this method, for example:
void aMethod(final List<String> list){
//some logic
}
The list passed inside this method cannot point to a new reference.
2.5 Using the final Keyword to Declare a Constant
Finally, you can use final with the static keyword to declare a constant. Below you can find some examples:
public static final double PI = 3.14;
public static final String CLH = "Code Learn Hub";
3. Using the final Keyword with a Method
You can also use the final keyword inside a method’s signature if you want to make sure that whoever chooses to extend the class that this method belongs to, will never override it.
Consider that we have these two classes:
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
final void printName(){
System.out.println(name);
}
}
public class Student extends Person {
private final double averageGrade;
public Student(String name, double averageGrade) {
super(name);
this.averageGrade = averageGrade;
}
}
Now if you do try to add the following inside the Student class:
@Override
void printName(){
//any code
}
You will get, as expected, the following compile-time error:
java: printName() in Student cannot override printName() in Person
overridden method is final
From the above, you can infer the following:
- You can never declare an abstract method inside an Abstract class as final as it is intended to be overridden by a subclass where the details of its implementation should exist.
- You can never declare a method of an Interface as final as it does not have any implementation and must be implemented by any class that will implement this interface.
- You cannot use the
default
keyword andfinal
together when declaring a default method inside an interface.
4. Using the final Keyword with a Class
If you declare a class as final, it means that this class can never have any descendants. So in the previous example, if we had declared the Person class as shown below:
public final class Person {
And, then, we tried to declare the Student class like before, we would get the following error:
java: cannot inherit from final Person
Consequently, the following are true:
- An abstract class can never be declared as final.
- An interface can never be declared as final.
- There is no point in adding the final keyword to any method of a class that is declared as final.
5. Making a Class Immutable Using the final Keyword
In this section, we’ll go through how you can make your class completely immutable before and after Java 14.
5.1 Before Java 14
Before Java 14, the record keyword did not exist so we need to manually create an immutable class as shown below:
public final class ImmutableClass {
private final int aNumber;
private final String aString;
private final List<String> list;
public int getaNumber() {
return aNumber;
}
public String getaString() {
return aString;
}
public List<String> getList() {
return list;
}
public ImmutableClass(int aNumber, String aString, List<String> list) {
this.aNumber = aNumber;
this.aString = aString;
this.list = Collections.unmodifiableList(list);
}
}
The this.list = Collections.unmodifiableList(list);
will not let anything change inside the list. In case you do not want that, you can replace it with this.list = list;
.
As a result of making our list unmodifiable, if you do try something like the following:
List<String> list = new ArrayList<>();
list.add("hello");
ImmutableClass immutableClass = new ImmutableClass(5, "hello", list);
immutableClass.getList().add("world");
The following run-time error would be produced:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.Collections$UnmodifiableCollection.add(Collections.java:1067)
at FinalKeywordExamples.main(FinalKeywordExamples.java:42)
5.2 Java 14 and After
Java 14 introduced records, which effectively are immutable classes. So the previous immutable class can be written in one line:
public record ImmutableRecord(int aNumber, String aString, List<String> list) {}
Of course, if you do need to disable any changes done to the list, you would have to override the default all-args constructor of the record:
public record ImmutableRecord(int aNumber, String aString, List<String> list) {
public ImmutableRecord(int aNumber, String aString, List<String> list) {
this.aNumber = aNumber;
this.aString = aString;
this.list = Collections.unmodifiableList(list);
}
}
6. Conclusion
By now, you should know exactly how the final keyword in Java behaves whether it’s used with a variable, a method, or a class. You can find the source code on my GitHub page.