What is the difference between a final class and a sealed class in Java?
Table of Contents
1. Short Answer
Final and sealed classes in Java serve different purposes in controlling class inheritance:
- Final Class: Cannot be extended by any other class. It completely prevents inheritance.
- Sealed Class: Can only be extended by classes that are explicitly permitted. It provides controlled inheritance.
2. Final Classes
Final classes are a fundamental feature of Java that completely prevent inheritance.
2.1 Characteristics
- Cannot be extended by any other class
- All methods are implicitly final
- Provides maximum security and immutability
- Used when you want to prevent any modifications to the class
2.2 Example
// Final class cannot be extended
public final class String {
// Class implementation
}
// This will cause a compilation error
// public class MyString extends String { }
Note
Final classes are often used for security-critical classes or when you want to ensure that the class behavior cannot be modified through inheritance.
3. Sealed Classes
Sealed classes were introduced in Java 17 to provide controlled inheritance.
3.1 Characteristics
- Can only be extended by explicitly permitted classes
- Provides controlled inheritance
- Improves code maintainability and security
- Used when you want to restrict inheritance to specific classes
3.2 Example
// Sealed class with permitted subclasses
public sealed class Shape permits Circle, Rectangle, Triangle {
// Common shape properties and methods
}
// Permitted subclasses
public final class Circle extends Shape {
// Circle implementation
}
public final class Rectangle extends Shape {
// Rectangle implementation
}
public final class Triangle extends Shape {
// Triangle implementation
}
// This will cause a compilation error
// public class Square extends Shape { }
Pro Tip
Sealed classes are particularly useful when implementing algebraic data types or when you need to ensure that a class hierarchy remains closed but still allows for some extension.
4. Key Differences
The following table summarizes the main differences between final and sealed classes:
| Feature | Final Class | Sealed Class |
|---|---|---|
| Inheritance | Completely prevented | Controlled and restricted |
| Flexibility | No flexibility | Controlled flexibility |
| Use Case | Security, immutability | Controlled extension |
| Java Version | Available since Java 1.0 | Introduced in Java 17 |
| Pattern Matching | Limited support | Better support with switch expressions |
5. Use Cases
Understanding when to use final vs sealed classes is crucial for good class design.
5.1 When to Use Final Classes
- Security-critical classes (e.g., String, Integer)
- Immutable classes
- Utility classes with static methods
- When you want to prevent any modifications
5.2 When to Use Sealed Classes
- Algebraic data types
- Domain models with fixed hierarchies
- When you need controlled extension
- Pattern matching with switch expressions
// Example of sealed class with pattern matching
public sealed interface Result permits Success, Failure {
// Common methods
}
public record Success(T value) implements Result { }
public record Failure(Exception error) implements Result { }
// Using pattern matching with sealed classes
public void handleResult(Result result) {
switch (result) {
case Success s -> System.out.println("Success: " + s.value());
case Failure f -> System.out.println("Error: " + f.error().getMessage());
}
}
6. Code Examples
Let's look at more practical examples of final and sealed classes.
6.1 Final Class Example
// Immutable final class
public final class ImmutablePoint {
private final int x;
private final int y;
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
// No setters, class is immutable
}
6.2 Sealed Class Example
// Sealed class hierarchy for a simple expression language
public sealed interface Expr permits Constant, Plus, Minus, Times {
int evaluate();
}
public record Constant(int value) implements Expr {
public int evaluate() { return value; }
}
public record Plus(Expr left, Expr right) implements Expr {
public int evaluate() { return left.evaluate() + right.evaluate(); }
}
public record Minus(Expr left, Expr right) implements Expr {
public int evaluate() { return left.evaluate() - right.evaluate(); }
}
public record Times(Expr left, Expr right) implements Expr {
public int evaluate() { return left.evaluate() * right.evaluate(); }
}
// Usage
Expr expr = new Plus(new Constant(5), new Times(new Constant(2), new Constant(3)));
int result = expr.evaluate(); // 11
7. Best Practices
Follow these best practices when working with final and sealed classes:
7.1 Final Class Best Practices
- Use for truly immutable classes
- Consider using for security-critical code
- Document why the class is final
- Ensure the class is well-designed before making it final
7.2 Sealed Class Best Practices
- Use for domain models with fixed hierarchies
- Keep the number of permitted subclasses manageable
- Consider using records for simple implementations
- Use pattern matching with sealed classes
Best Practice
When designing a class hierarchy, consider whether you need complete prevention of inheritance (final) or controlled inheritance (sealed). Sealed classes often provide a better balance between flexibility and control.
8. Conclusion
Understanding the differences between final and sealed classes is essential for effective class design in Java.
Key takeaways:
- Final classes completely prevent inheritance
- Sealed classes allow controlled inheritance
- Choose the right approach based on your needs
- Follow best practices for both approaches
- Consider using sealed classes with pattern matching