Object-Oriented Programming: Sealed Classes & Domain Hierarchy Control
Key Takeaways
Sealed classes allow a superclass to explicitly control and restrict which subclasses are permitted to extend it, providing domain modeling boundaries within an inheritance hierarchy. Declared using the sealed modifier followed by a permits clause, sealed classes require that every permitted subclass directly extends the superclass and explicitly defines its own inheritance boundary. Every permitted subclass must declare itself as final, sealed, or non-sealed, ensuring the inheritance hierarchy is exhaustively defined and bounded.
- Restricted Inheritance: Sealed classes restrict inheritance by granting extension permissions exclusively to a comma-delimited list of classes in a
permitsclause. - Permitted Subclass Obligations: Every subclass listed in a
permitsclause must directly extend the sealed parent class. - The Three Subclass Modifiers: Each permitted subclass must explicitly declare exactly one of three modifiers:
final: Closes inheritance permanently; no class may extend it.sealed: Continues controlled inheritance by permitting its own explicit list of subclasses.non-sealed: Re-opens inheritance; any class can extend it without restriction.
- Unbounded Descendants of
non-sealed: Classes extending anon-sealedclass operate under standard Java inheritance rules and are not obligated to declarefinal,sealed, ornon-sealed.
Main Discussion
Idiomatic Java Code Snippets
1. Declaring the Root Sealed Class (Shape)
The superclass uses the sealed reserved word and explicitly enumerates its allowed direct subclasses using permits:
package sealed_hierarchy;
// Root sealed class permitting only Circle and Rectangle to extend it
public sealed class Shape permits Circle, Rectangle {
public void draw() {
System.out.println("Drawing a shape");
}
}
2. Defining Permitted Subclasses with Required Modifiers
Each direct subclass must extend Shape and declare whether it is non-sealed, sealed, or final:
package sealed_hierarchy;
// Re-opens inheritance: any class in the program can extend Circle
public non-sealed class Circle extends Shape {
private double radius;
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
}
package sealed_hierarchy;
// Continues restricted inheritance: permits only Square to extend it
public sealed class Rectangle extends Shape permits Square {
private double length;
private double width;
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
}
package sealed_hierarchy;
// Closes the hierarchy: Square cannot be extended by any class
public final class Square extends Rectangle {
public Square(double side) {
setLength(side);
setWidth(side);
}
}
3. Extending a Non-Sealed Class
Classes extending Circle return to standard inheritance rules:
package sealed_hierarchy;
// Standard inheritance: no obligation to declare final, sealed, or non-sealed
public class ColoredCircle extends Circle {
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
Under the Hood / JVM Mechanics
-
Permitted Subclass Verification at Compile Time: During compilation of Shape.java, javac reads the permits clause and confirms that Circle and Rectangle exist and explicitly extend Shape. If a permitted class does not extend the sealed class, compilation halts with an error.
-
Subclass Modifier Enforcement: The compiler verifies that every direct subclass carries exactly one of the three required modifiers (final, sealed, or non-sealed). Omitting a modifier produces a compile-time failure.
-
PermittedSubclasses Bytecode Attribute: The compiler injects a PermittedSubclasses attribute into the compiled .class file metadata of the sealed class, listing the authorized binary class symbols.
-
Unauthorized Subclass Rejection: If an unlisted class attempts to extend Shape (e.g., class Triangle extends Shape), the compiler and the JVM's class verification engine reject it, preventing unauthorized extensions.
Structural Comparisons
The Three Permitted Subclass Modifiers
| Modifier | Effect on Inheritance | Subclass Must Have permits? | Future Extensions Allowed? |
|---|---|---|---|
final | Permanently terminates inheritance | No (cannot have subclasses) | No (leaf node) |
sealed | Continues restricted inheritance | Yes (must define allowed subclasses) | Yes, but only to permitted classes |
non-sealed | Re-opens unrestricted inheritance | No | Yes, open to any class |
Standard Class vs. Sealed Class
| Feature | Standard Class (class) | Sealed Class (sealed class) |
|---|---|---|
| Inheritance Scope | Open to any class (unless marked final) | Restricted to explicitly permitted classes |
| Permits Clause | Not supported | Required when subclasses exist in separate files |
| Subclass Constraints | Subclasses can be normal, abstract, or final | Subclasses must declare final, sealed, or non-sealed |
Common Anti-Patterns & Edge Cases
- Missing Permitted Subclass Modifier: Declaring a permitted subclass without
final,sealed, ornon-sealed(e.g.,public class Circle extends Shape) triggers a compile-time failure:sealed, non-sealed or final modifiers expected. - Permitting an Unrelated Class: Listing a class in the
permitsclause that does not declareextends [SealedClass]causes a compilation error:class is not allowed to extend sealed class. - Extending Without Permission: Creating
public final class Triangle extends ShapewhenTriangleis not listed inShape'spermitsclause causes a compile error:class not allowed to extend sealed class: sealed_hierarchy.Shape. - Attempting to Extend a
finalSubclass: Creating a class that extends a subclass markedfinal(e.g.,class MiniSquare extends Square) fails compilation:cannot inherit from final sealed_hierarchy.Square.
Knowledge Check & JetBrains-Style Coding Challenges
Theory Tips
- The Modifier Triad: Every class directly extending a
sealedclass must be declared with exactly one of three modifiers:final,sealed, ornon-sealed. - The Non-Sealed Escape: The
non-sealedmodifier is a hyphenated keyword that breaks out of the sealed restriction, allowing downstream classes to extend it without restriction. - Mutual Contract: A sealed class must list its subclasses in
permits, and each permitted subclass must explicitly declare that itextendsthe sealed class.
Question 1: Conceptual / Code Analysis
Consider the following proposed class structure:
public sealed class Vehicle permits Car, Truck { }
public class Car extends Vehicle { }
public final class Truck extends Vehicle { }
What occurs when attempting to compile this code?
- A Compilation succeeds;
Cardefaults to an open subclass andTruckterminates inheritance. - B A compilation error occurs because
Carfails to declare itself assealed,non-sealed, orfinal. - C A compilation error occurs because
Vehiclemust declare at least one abstract method. - D A runtime error occurs when loading
VehiclebecauseCaris not markedfinal.
Correct Answer
Correct Answer: B
Technical Breakdown:
- B is correct: Java language specifications strictly require that any class directly extending a sealed class must explicitly declare its own inheritance posture using one of three modifiers:
final,sealed, ornon-sealed. BecauseCaris declared simply aspublic class Car, compilation fails with an error:sealed, non-sealed or final modifiers expected. - A is incorrect: Java does not assume a default inheritance posture for children of sealed classes.
- C is incorrect: Sealed classes do not require abstract methods.
- D is incorrect: This constraint is checked at compile time by
javac, not at runtime.
Question 2: Mini Coding Problem / Implementation Challenge
Task: Model a document service hierarchy using sealed classes inside package sealed_hierarchy:
- Sealed superclass
Document:- Private field
title(String). - Constructor
public Document(String title)initializing the field. - Public getter
getTitle(). - Permits two direct subclasses:
TextDocumentandSpreadsheetDocument.
- Private field
- Permitted subclass
TextDocument:- Must be declared
final. - Has a constructor passing
titletosuper(title).
- Must be declared
- Permitted subclass
SpreadsheetDocument:- Must be declared
non-sealed. - Has a constructor passing
titletosuper(title).
- Must be declared
- Class
FinancialSheetextendingSpreadsheetDocument:- Demonstrates extending an open
non-sealedclass without requiring special sealed modifiers. - Constructor passing
titletosuper(title).
- Demonstrates extending an open
Test Assertions:
Document doc = new TextDocument("Report.docx");doc.getTitle()returns"Report.docx"FinancialSheet sheet = new FinancialSheet("Q3_Budget.xlsx");sheet.getTitle()returns"Q3_Budget.xlsx"
Solution
package sealed_hierarchy;
// Sealed root restricting direct extension to TextDocument and SpreadsheetDocument
public sealed class Document permits TextDocument, SpreadsheetDocument {
private String title;
public Document(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
}
package sealed_hierarchy;
// Final permitted subclass: terminates further inheritance
public final class TextDocument extends Document {
public TextDocument(String title) {
super(title);
}
}
package sealed_hierarchy;
// Non-sealed permitted subclass: re-opens inheritance
public non-sealed class SpreadsheetDocument extends Document {
public SpreadsheetDocument(String title) {
super(title);
}
}
package sealed_hierarchy;
// Normal class extending the non-sealed class without inheritance restrictions
public class FinancialSheet extends SpreadsheetDocument {
public FinancialSheet(String title) {
super(title);
}
}
Complexity Analysis:
- Time Complexity: for class instantiation and method execution, as constructor chaining and field assignment operate in constant time.
- Space Complexity: auxiliary memory allocated beyond the standard heap allocation for object instances.