Deriving Permitted Direct Subtypes of a Sealed Supertype
The permits clause of a sealed class or interface can be omitted, if all its permitted direct subtypes are declared in the same compilation unit—that is, are in the same source file. Since only one reference type can be declared public in a compilation unit, the accessibility of the other type declarations cannot be public.
The compiler can infer the permitted direct subtypes of any sealed class or interface in a compilation unit by checking whether a type declaration fulfills the contract for declaring a permitted direct subtype; that is, it extends/implements a sealed supertype and is declared either (implicitly) sealed, non-sealed, or (implicitly) final.
// File: Book.java (compilation unit)
public abstract sealed class Book {} // Permitted subclasses are derived.
non-sealed class PrintedBook extends Book {}
sealed interface Subscribable {} // Permitted subtypes are derived.
final class Ebook extends Book implements Subscribable {}
final class Audiobook extends Book implements Subscribable {}
non-sealed interface VIPSubcribable extends Subscribable {}
Given the type declarations in the compilation unit Book.java, the compiler is able to infer that PrintedBook, Ebook, and Audiobook are permitted direct subclasses of the sealed superclass Book, whereas Ebook, Audiobook, and VIPSubcribable are permitted direct subtypes of the sealed superinterface Subscribable.
Using Sealed Classes and Interfaces
As we have seen, sealed classes and interfaces restrict which other classes or interfaces can extend or implement them. Apart from providing the programmer with a language feature to develop improved domain models for libraries, the compiler can also leverage the sealed types to provide better analysis of potential problems in the code at compile time.
Example 5.33 is a client that uses the sealed classes and their permitted direct subclasses defined in Example 5.32. Typically, a reference of the sealed superclass is used to process a collection of objects of its permitted subtypes. The array books is such a collection created at (2). The objects of the permitted subtypes are processed by the enhanced for(:) loop at (3). Typically, a cascading if-else statement is used in the loop body to distinguish each object in the collection and process it accordingly. Any object of a permitted subclass can be processed by the code as long as the cascading if-else statement guarantees that the checks are exhaustive—that is, all permitted subtypes are covered. The compiler cannot help to ensure that this is the case, as it cannot extract the necessary information from the cascading if-else statement for this analysis. Note that the conditionals in the cascading if-else statement use the instanceof pattern match operator to make the code at least less verbose.
Example 5.33 Using Sealed Classes
package signedandsealed;
public class BookAdmin {
public static void main(String[] args) {
// Create some books: // (1)
PrintedBook pBook = new PrintedBook(“888-222”, 340);
Ebook eBook = new Ebook(“999-777”, “epub”);
Audiobook aBook = new Audiobook(“333-555”, “Fry”, 300.0);
// Create a book array: // (2)
Book[] books = {pBook, eBook, aBook};
// Process the books: // (3)
for (Book book : books) {
if (book instanceof PrintedBook pb) { // (4)
System.out.printf(“Printed book: %s, %d%n”,
pb.getIsbn(), pb.getPageCount());
} else if (book instanceof Ebook eb) {
System.out.printf(“Ebook: %s, %s%n”, eb.getIsbn(), eb.getFormat());
} else if (book instanceof Audiobook ab) {
System.out.printf(“Audiobook: %s, %s, %.1f%n”,
ab.getIsbn(), ab.getNarrator(), ab.getLength());
}
}
}
}
Output from the program:
Click here to view code image Printed book: 888-222, 340
Ebook: 999-777, epub
Audiobook: 333-555, Fry, 300.0