SyntaxStudy
Sign Up
Java final Classes, final Methods, and Preventing Inheritance
Java Beginner 1 min read

final Classes, final Methods, and Preventing Inheritance

The final modifier, when applied to a class, prevents any other class from extending it. Final classes are used when the designer wants to ensure the class behaviour cannot be altered through subclassing. The String class is a well-known final class: it would be dangerous to allow subclasses that could change how strings behave in security-critical code. The final modifier on a method prevents subclasses from overriding that method. This is useful when a method must not be altered because it is part of a security guarantee or a precisely defined algorithm. Abstract classes often combine abstract methods (must be overridden) with final methods (must not be overridden) to enforce a contract. Choosing between inheritance and composition is an important design decision. Inheritance is appropriate for genuine "is-a" relationships where the subclass truly is a more specific kind of the superclass. Composition (holding a reference to another object) is often preferable when the relationship is "has-a" or when you want to combine behaviours from multiple sources. Favour composition over inheritance is a well-known design principle that leads to more flexible, loosely coupled code.
Example
// Final class — cannot be subclassed
final class ImmutableVector {
    private final double x, y, z;

    ImmutableVector(double x, double y, double z) {
        this.x = x; this.y = y; this.z = z;
    }

    public double getX() { return x; }
    public double getY() { return y; }
    public double getZ() { return z; }

    public ImmutableVector add(ImmutableVector other) {
        return new ImmutableVector(x + other.x, y + other.y, z + other.z);
    }

    public double magnitude() {
        return Math.sqrt(x * x + y * y + z * z);
    }

    @Override public String toString() {
        return String.format("Vector(%.2f, %.2f, %.2f)", x, y, z);
    }
}

// Composition over inheritance example
class Logger {
    void log(String message) {
        System.out.println("[LOG] " + message);
    }
}

class UserService {  // HAS-A Logger, not IS-A Logger
    private final Logger logger = new Logger();

    public String findUser(int id) {
        logger.log("Looking up user " + id);
        return "User-" + id; // simplified
    }
}

public class FinalAndComposition {
    // Class attempting to extend ImmutableVector would be a compile error:
    // class ExtendedVector extends ImmutableVector { }  // ERROR

    public static void main(String[] args) {
        ImmutableVector v1 = new ImmutableVector(1, 2, 3);
        ImmutableVector v2 = new ImmutableVector(4, 5, 6);
        ImmutableVector v3 = v1.add(v2);

        System.out.println("v1: " + v1);
        System.out.println("v2: " + v2);
        System.out.println("v1 + v2: " + v3);
        System.out.printf("|v1| = %.4f%n", v1.magnitude());

        UserService svc = new UserService();
        System.out.println(svc.findUser(42));
    }
}