Inheritance & Polymorphism

The final Keyword for Classes & Methods

15 min Lesson 7 of 14

The final Keyword for Classes & Methods

So far in this tutorial you have learned how to extend classes and override methods to build flexible hierarchies. But sometimes flexibility is the wrong goal. Sometimes you want to say: "This class must never be subclassed" or "This method must never be replaced." Java gives you a single keyword for both purposes: final.

What final means on a method

When you mark a method final, no subclass can override it. The implementation you wrote is locked in place for the entire inheritance chain.

class BankAccount { private double balance; public BankAccount(double initialBalance) { this.balance = initialBalance; } // Subclasses MAY override how interest is calculated... public double calculateInterest() { return balance * 0.02; } // ...but they must NEVER change how a withdrawal is validated. public final boolean withdraw(double amount) { if (amount <= 0 || amount > balance) { return false; } balance -= amount; return true; } } class SavingsAccount extends BankAccount { public SavingsAccount(double initialBalance) { super(initialBalance); } @Override public double calculateInterest() { // allowed — not final return 0.05; } // @Override // public boolean withdraw(double amount) { ... } // COMPILE ERROR: cannot override final method withdraw() }

The withdrawal logic enforces a business rule: you cannot withdraw a negative amount or more than your balance. Making it final guarantees that no rogue subclass can bypass that rule.

Key idea: Use final on a method when correct behaviour depends on that method doing exactly what it does — and a subclass overriding it would introduce bugs or security holes.

What final means on a class

When you mark an entire class final, it cannot be extended at all. Nobody can write class Foo extends YourFinalClass.

public final class ImmutablePoint { private final double x; private final double y; public ImmutablePoint(double x, double y) { this.x = x; this.y = y; } public double getX() { return x; } public double getY() { return y; } public ImmutablePoint translate(double dx, double dy) { return new ImmutablePoint(x + dx, y + dy); // returns a NEW point } } // class MutablePoint extends ImmutablePoint { ... } // COMPILE ERROR: cannot inherit from final ImmutablePoint

Because the class is final and all its fields are private final, an ImmutablePoint object is guaranteed to be immutable — its coordinates can never change after construction, and no subclass can sneak in mutable state.

Java's own final classes — String

String is the most famous example of a final class in the Java standard library. You cannot extend it:

// class MyString extends String { ... } // COMPILE ERROR: cannot inherit from final String

Why did the Java designers make String final? Several reasons work together:

  • Security. Many parts of the JVM and standard library receive a String and trust its value (file paths, class names, passwords). If String were extendable, a malicious subclass could override equals or toString to lie about its value after a security check.
  • Immutability. String objects are immutable — their internal char[] (or byte[] in modern Java) never changes. Immutability is only guaranteed if subclasses cannot add mutable fields.
  • String interning & the constant pool. The JVM caches String literals. That optimisation is safe only because all strings behave identically; subclasses would break the guarantee.
Other notable final classes in the JDK: Integer, Long, Double, and all the other primitive wrapper types are final for the same immutability and correctness reasons. So is Math.

final fields — a quick reminder

You have already seen final on fields (a variable that can only be assigned once). That is a separate — but related — use of the same keyword. All three uses share one philosophy: commit to a value or a behaviour and prevent it from changing.

class Circle { private final double radius; // field: assigned once in constructor, never changed public Circle(double radius) { this.radius = radius; } public final double area() { // method: no subclass may override this formula return Math.PI * radius * radius; } }

When should you use final?

A useful mental checklist:

  • Mark a method final when it implements a security rule, a protocol step, or an algorithm that subclasses must rely on but must not alter (e.g. a template-method skeleton that calls other overridable hooks, but whose orchestration is fixed).
  • Mark a class final when the class represents a value type that must stay immutable (like String or Integer), or when the class is a utility class (like Math) that should never be instantiated through a subclass.
  • Do not overuse final. Marking everything final makes your code harder to test (you cannot create test doubles) and harder to extend when requirements change.
Common pitfall: Developers sometimes mark a class final to prevent misuse, only to discover later that a legitimate use-case needs extension. Prefer designing with good access modifiers and encapsulation first. Reach for final when you have a concrete reason — not just as a habit.

Compile-time enforcement

final is checked entirely at compile time — there is no runtime overhead. If you try to extend a final class or override a final method, the compiler rejects the code immediately with a clear error message. This makes it a zero-cost safety net.

Summary

The final keyword on a method prevents any subclass from overriding that method, locking a specific behaviour in place. On a class it prevents the class from being extended at all, which is how Java guarantees that String, Integer, and similar types stay immutable and trustworthy. Use final deliberately: to enforce a contract, to preserve immutability, or to protect a security-critical implementation.