Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 3

Understanding the 'final' Modifier

Recommended resources

Introduction

In our last lesson, we explored how the static keyword creates fields and methods that are shared across all instances of a class. We even had a glimpse of the public static final combination, a common pattern for defining constants. Today, we'll focus on that last piece: the final keyword.

This lesson is dedicated to understanding how the final modifier acts as a tool for enforcing design constraints in Java. You will learn how to apply final in three distinct contexts: to create variables whose values cannot be changed, methods that cannot be overridden, and classes that cannot be extended. Mastering final is essential for writing secure, predictable, and robust code, a skill that underpins many advanced design patterns you'll encounter later in this course.


The Three Roles of the final Keyword

The final keyword in Java is all about imposing a restriction; it makes something "final" in the sense that it cannot be changed later. Its meaning, however, depends entirely on where it is applied.

This diagram succinctly shows the three applications of the `final` keyword: preventing value changes for variables, preventing overriding for methods, and preventing inheritance for classes.

We will explore each of these uses, starting with variables.


1. Final Variables: Creating Constants

The most straightforward use of final is with variables. When you declare a variable as final, you can assign a value to it only once. After that, its value is locked in and cannot be changed. This is how you create constants in Java.

In the previous lesson, we discussed the need for shared data using static. When you combine static with final, you create a true class-level constant, accessible everywhere and guaranteed not to change.

The following video provides an excellent walkthrough of all three uses of final, but we'll start with its application to variables. Pay close attention to how public static final is used to create a constant for PI and the naming convention (all caps) associated with it.

Final Keyword in Java Full Tutorial - Final Classes, Methods, and Variables

This tutorial from Coding with John clearly demonstrates the practical uses of the final keyword.

Please watch the section on final variables, from the beginning. The video explains how final prevents reassignment and shows the common pattern for creating public static final constants.

A Crucial Distinction: Final References

There's a very important nuance when using final with object references. Declaring a reference variable final means the reference itself cannot be changed to point to a different object. However, it does not mean the object it points to is immutable. You can still call methods on the object that change its internal state.

Let's read a short but critical explanation from Baeldung to solidify this point.

The "final" Keyword in Java | Baeldung

This article provides a detailed breakdown of the final keyword. We'll focus on how it applies to variables.

Please read section 4, "Final Variables". Pay special attention to subsection 4.2, "Final Reference Variables", which highlights the difference between a final reference and an immutable object.

To see this in code, consider this example from GeeksforGeeks, which uses a StringBuilder.

// From GeeksforGeeks
final StringBuilder sb = new StringBuilder("Geeks");
System.out.println(sb); // Prints "Geeks"

// This is allowed! We are changing the internal state of the object.
sb.append("ForGeeks");
System.out.println(sb); // Prints "GeeksForGeeks"

// This would cause a compiler error because sb is final.
// sb = new StringBuilder("Another one"); 

This distinction is vital. final helps in building immutable objects, but it is not sufficient on its own.


2. Final Methods: Preventing Overriding

The next application of final is on methods. When you declare a method as final, you prevent any subclass from overriding it.

Why would you do this? Sometimes, a method in a superclass contains a critical algorithm or follows a strict contract that must not be altered by subclasses. Making the method final ensures its implementation remains consistent across all child classes, preventing unexpected behavior from polymorphism.

Let's return to the "Coding with John" video to see this in action. The example shows how a Dog class can normally override the eat() method from its Animal parent, and how final stops this.

Final Keyword in Java Full Tutorial - Final Classes, Methods, and Variables

We'll continue with the same video tutorial.

Now, watch the section on final methods, from this timestamp. It clearly demonstrates how a final method in a parent class cannot be overridden in a child class.

A great example from the Java Development Kit (JDK) is the Thread.isAlive() method. This method checks if a thread is actively running. Its implementation is native (tied to the operating system) and complex. Allowing it to be overridden would be dangerous and could easily lead to incorrect behavior, so it is declared final.


3. Final Classes: Preventing Inheritance

Finally, you can declare an entire class as final. A final class cannot be extended. No other class can inherit from it.

This is the ultimate restriction. It's used for classes whose implementations are considered complete and unchangeable, often for security and stability reasons. The most famous example in Java is the String class. String objects are used everywhere, and the entire platform relies on their predictable, immutable behavior. If programmers could create their own subclasses of String and alter its methods, it could compromise the stability and security of countless applications.

Let's watch the last segment from the video, which demonstrates this concept clearly.

Final Keyword in Java Full Tutorial - Final Classes, Methods, and Variables

One last time with this video.

Watch the first main segment on final classes, from the beginning. This will show you the compiler error that results from trying to extend a final class.

Making a class final is a significant design decision. While it provides safety, it also removes the possibility of extension, one of the key benefits of object-oriented programming. You should only do it when you are certain that the class's behavior should never be modified or extended.


Connecting the Dots: final and Immutability

As we've seen, final is a key ingredient in creating immutable objects—objects whose state cannot be modified after creation. True immutability requires more than just final fields, but it's a critical starting point.

This flowchart shows the requirements for an immutable class in Java. Notice that using `final` for the class and its fields are two of the four essential rules.

An immutable class should:

  1. Be declared final to prevent subclasses from altering its behavior.
  2. Have all its fields be private and final to prevent direct modification.
  3. Not provide any "setter" methods that modify the fields.
  4. If it contains references to mutable objects (like a Date or a List), it must perform "defensive copying" to ensure external code can't change its internal state.

We will revisit immutability later, as it's a powerful concept for building simple and thread-safe components. For now, recognize that the final keyword is your primary tool for enforcing these kinds of design guarantees.


Conclusion

In this lesson, we've unpacked the final keyword and its three distinct roles in Java. By using it correctly, you can write code that is more predictable, secure, and easier to reason about.

Here are the key takeaways:

  • A final variable can only be assigned once, making it a constant. For class-level constants, the convention is public static final.
  • A final method cannot be overridden by any subclass, ensuring its implementation is fixed.
  • A final class cannot be extended (inherited from), which is used for security and to guarantee implementation stability (e.g., String).
  • Remember that a final reference variable does not make the object it points to immutable; it only prevents the reference from pointing to a different object.

We have now covered several key modifiers—public, private, static, and final—that control access, sharing, and modification. In our next lesson, we will build on this foundation to explore one of the most powerful concepts in object-oriented design: interfaces. You will learn how to define and implement interfaces, which are central to achieving polymorphism and building flexible, decoupled systems.