Add sealed module: modelling domains with sealed types and exhaustive switch

Payment model, permits and the three modifiers, default-branch trap, generic and recursive hierarchies, visitor comparison, run-time enforcement, modules. Transcripts from JDK 21, 25 and 27.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TF9JWFvJSNm6HVzswzZU5a
This commit is contained in:
Claude
2026-09-24 12:03:59 +00:00
parent 65a4bbbbe3
commit ff6130bede
38 changed files with 638 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
$ javac src/PaymentDemo.java && java PaymentDemo (21.0.10+7-Ubuntu-124.04)
Card[last4=4242, amount=500] -> fee 10 | card payment ending 4242
Card[last4=1111, amount=25000] -> fee 500 | large card payment ending 1111
Upi[vpa=ankur@bank, amount=500] -> fee 0 | UPI payment from ankur@bank
NetBanking[bank=HDFC, amount=500] -> fee 15 | net banking via HDFC
$ javac src/PaymentDemo.java && java PaymentDemo (25.0.4.1+1-LTS)
Card[last4=4242, amount=500] -> fee 10 | card payment ending 4242
Card[last4=1111, amount=25000] -> fee 500 | large card payment ending 1111
Upi[vpa=ankur@bank, amount=500] -> fee 0 | UPI payment from ankur@bank
NetBanking[bank=HDFC, amount=500] -> fee 15 | net banking via HDFC
$ javac src/PaymentDemo.java && java PaymentDemo (27+35)
Card[last4=4242, amount=500] -> fee 10 | card payment ending 4242
Card[last4=1111, amount=25000] -> fee 500 | large card payment ending 1111
Upi[vpa=ankur@bank, amount=500] -> fee 0 | UPI payment from ankur@bank
NetBanking[bank=HDFC, amount=500] -> fee 15 | net banking via HDFC
@@ -0,0 +1,44 @@
$ javac src/Reflect.java && java Reflect (21.0.10+7-Ubuntu-124.04)
Shape: sealed, permits [Circle, Poly, Custom]
Circle: final
Poly: sealed, permits [Tri, Quad]
Tri: final
Quad: final
Custom: open (non-sealed or plain)
Hexagon: open (non-sealed or plain)
circle triangle quad custom:Hexagon
$ javac src/Reflect.java && java Reflect (25.0.4.1+1-LTS)
Shape: sealed, permits [Circle, Poly, Custom]
Circle: final
Poly: sealed, permits [Tri, Quad]
Tri: final
Quad: final
Custom: open (non-sealed or plain)
Hexagon: open (non-sealed or plain)
circle triangle quad custom:Hexagon
$ javac src/Reflect.java && java Reflect (27+35)
Shape: sealed, permits [Circle, Poly, Custom]
Circle: final
Poly: sealed, permits [Tri, Quad]
Tri: final
Quad: final
Custom: open (non-sealed or plain)
Hexagon: open (non-sealed or plain)
circle triangle quad custom:Hexagon
$ javac src/ImplicitPermits.java && java ImplicitPermits (21.0.10+7-Ubuntu-124.04)
[ImplicitPermits$A, ImplicitPermits$B]
$ javac src/ImplicitPermits.java && java ImplicitPermits (25.0.4.1+1-LTS)
[ImplicitPermits$A, ImplicitPermits$B]
$ javac src/ImplicitPermits.java && java ImplicitPermits (27+35)
[ImplicitPermits$A, ImplicitPermits$B]
$ javap -v -cp . 'PaymentDemo$Payment' | grep -A3 PermittedSubclasses (25.0.4.1+1-LTS)
PermittedSubclasses:
PaymentDemo$Card
PaymentDemo$Upi
PaymentDemo$NetBanking
+130
View File
@@ -0,0 +1,130 @@
$ javac broken/WalletNotHandled.java (21.0.10+7-Ubuntu-124.04)
broken/WalletNotHandled.java:10: error: the switch expression does not cover all possible input values
return switch (p) {
^
1 error
$ javac broken/WalletNotHandled.java (25.0.4.1+1-LTS)
broken/WalletNotHandled.java:10: error: the switch expression does not cover all possible input values
return switch (p) {
^
1 error
$ javac broken/WalletNotHandled.java (27+35)
broken/WalletNotHandled.java:10: error: the switch expression does not cover all possible input values
return switch (p) {
^
missing patterns:
Wallet _
1 error
$ javac broken/NotPermitted.java (21.0.10+7-Ubuntu-124.04)
broken/NotPermitted.java:5: error: class is not allowed to extend sealed class: Payment (as it is not listed in its 'permits' clause)
record Cheque(long amount) implements Payment {} // not in the permits clause
^
1 error
$ javac broken/NotPermitted.java (25.0.4.1+1-LTS)
broken/NotPermitted.java:5: error: class is not allowed to extend sealed class: Payment (as it is not listed in its 'permits' clause)
record Cheque(long amount) implements Payment {} // not in the permits clause
^
1 error
$ javac broken/NotPermitted.java (27+35)
broken/NotPermitted.java:5: error: class is not allowed to extend sealed class: Payment (as it is not listed in its 'permits' clause)
record Cheque(long amount) implements Payment {} // not in the permits clause
^
1 error
$ javac broken/MissingModifier.java (21.0.10+7-Ubuntu-124.04)
broken/MissingModifier.java:3: error: sealed, non-sealed or final modifiers expected
class Card implements Payment {} // must say final, sealed or non-sealed
^
1 error
$ javac broken/MissingModifier.java (25.0.4.1+1-LTS)
broken/MissingModifier.java:3: error: sealed, non-sealed or final modifiers expected
class Card implements Payment {} // must say final, sealed or non-sealed
^
1 error
$ javac broken/MissingModifier.java (27+35)
broken/MissingModifier.java:3: error: sealed, non-sealed or final modifiers expected
class Card implements Payment {} // must say final, sealed or non-sealed
^
1 error
$ javac broken/NoSubclass.java (21.0.10+7-Ubuntu-124.04)
broken/NoSubclass.java:2: error: sealed class must have subclasses
sealed interface Payment {} // no permits clause and no subtype in this file
^
1 error
$ javac broken/NoSubclass.java (25.0.4.1+1-LTS)
broken/NoSubclass.java:2: error: sealed class must have subclasses
sealed interface Payment {} // no permits clause and no subtype in this file
^
1 error
$ javac broken/NoSubclass.java (27+35)
broken/NoSubclass.java:2: error: sealed class must have subclasses
sealed interface Payment {} // no permits clause and no subtype in this file
^
1 error
$ javac broken/NonSealedWrongPlace.java (21.0.10+7-Ubuntu-124.04)
broken/NonSealedWrongPlace.java:3: error: non-sealed modifier not allowed here
non-sealed class Oops implements Plain {} // non-sealed only makes sense under a sealed supertype
^
(class NonSealedWrongPlace.Oops does not have any sealed supertypes)
1 error
$ javac broken/NonSealedWrongPlace.java (25.0.4.1+1-LTS)
broken/NonSealedWrongPlace.java:3: error: non-sealed modifier not allowed here
non-sealed class Oops implements Plain {} // non-sealed only makes sense under a sealed supertype
^
(class NonSealedWrongPlace.Oops does not have any sealed supertypes)
1 error
$ javac broken/NonSealedWrongPlace.java (27+35)
broken/NonSealedWrongPlace.java:3: error: non-sealed modifier not allowed here
non-sealed class Oops implements Plain {} // non-sealed only makes sense under a sealed supertype
^
(class NonSealedWrongPlace.Oops does not have any sealed supertypes)
1 error
$ javac broken/AnonymousSealed.java (21.0.10+7-Ubuntu-124.04)
broken/AnonymousSealed.java:5: error: anonymous classes must not extend sealed classes
return new Payment() {}; // an anonymous class is a subtype nobody listed
^
1 error
$ javac broken/AnonymousSealed.java (25.0.4.1+1-LTS)
broken/AnonymousSealed.java:5: error: anonymous classes must not extend sealed classes
return new Payment() {}; // an anonymous class is a subtype nobody listed
^
1 error
$ javac broken/AnonymousSealed.java (27+35)
broken/AnonymousSealed.java:5: error: anonymous classes must not extend sealed classes
return new Payment() {}; // an anonymous class is a subtype nobody listed
^
1 error
$ javac broken/xpkg/a/Base.java broken/xpkg/b/Impl.java (21.0.10+7-Ubuntu-124.04)
broken/xpkg/a/Base.java:2: error: class Base in unnamed module cannot extend a sealed class in a different package
public sealed interface Base permits b.Impl {}
^
1 error
$ javac broken/xpkg/a/Base.java broken/xpkg/b/Impl.java (25.0.4.1+1-LTS)
broken/xpkg/a/Base.java:2: error: class Base in unnamed module cannot extend a sealed class in a different package
public sealed interface Base permits b.Impl {}
^
1 error
$ javac broken/xpkg/a/Base.java broken/xpkg/b/Impl.java (27+35)
broken/xpkg/a/Base.java:2: error: class Base in unnamed module cannot extend a sealed class in a different package
public sealed interface Base permits b.Impl {}
^
1 error
+8
View File
@@ -0,0 +1,8 @@
$ javac src/WithDefault.java && java WithDefault (21.0.10+7-Ubuntu-124.04)
fee for Wallet(1000) = 15 (the rule for Wallet is 1 percent, so 10; it compiled without any complaint)
$ javac src/WithDefault.java && java WithDefault (25.0.4.1+1-LTS)
fee for Wallet(1000) = 15 (the rule for Wallet is 1 percent, so 10; it compiled without any complaint)
$ javac src/WithDefault.java && java WithDefault (27+35)
fee for Wallet(1000) = 15 (the rule for Wallet is 1 percent, so 10; it compiled without any complaint)
@@ -0,0 +1,26 @@
$ javac src/ResultDemo.java && java ResultDemo (21.0.10+7-Ubuntu-124.04)
21 -> ok 42
abc -> error: not a number: abc
$ javac src/ResultDemo.java && java ResultDemo (25.0.4.1+1-LTS)
21 -> ok 42
abc -> error: not a number: abc
$ javac src/ResultDemo.java && java ResultDemo (27+35)
21 -> ok 42
abc -> error: not a number: abc
$ javac src/ExprDemo.java && java ExprDemo (21.0.10+7-Ubuntu-124.04)
Add[left=Mul[left=Num[value=1], right=Num[value=7]], right=Neg[inner=Neg[inner=Num[value=3]]]]
eval = 10
simplify = Add[left=Num[value=7], right=Num[value=3]] (eval 10)
$ javac src/ExprDemo.java && java ExprDemo (25.0.4.1+1-LTS)
Add[left=Mul[left=Num[value=1], right=Num[value=7]], right=Neg[inner=Neg[inner=Num[value=3]]]]
eval = 10
simplify = Add[left=Num[value=7], right=Num[value=3]] (eval 10)
$ javac src/ExprDemo.java && java ExprDemo (27+35)
Add[left=Mul[left=Num[value=1], right=Num[value=7]], right=Neg[inner=Neg[inner=Num[value=3]]]]
eval = 10
simplify = Add[left=Num[value=7], right=Num[value=3]] (eval 10)
+41
View File
@@ -0,0 +1,41 @@
$ javac src/VisitorVersion.java && java VisitorVersion (21.0.10+7-Ubuntu-124.04)
Card[last4=4242, amount=500] -> fee 10
Upi[vpa=ankur@bank, amount=500] -> fee 0
NetBanking[bank=HDFC, amount=500] -> fee 15
$ javac src/VisitorVersion.java && java VisitorVersion (25.0.4.1+1-LTS)
Card[last4=4242, amount=500] -> fee 10
Upi[vpa=ankur@bank, amount=500] -> fee 0
NetBanking[bank=HDFC, amount=500] -> fee 15
$ javac src/VisitorVersion.java && java VisitorVersion (27+35)
Card[last4=4242, amount=500] -> fee 10
Upi[vpa=ankur@bank, amount=500] -> fee 0
NetBanking[bank=HDFC, amount=500] -> fee 15
$ javac broken/VisitorAdded.java (21.0.10+7-Ubuntu-124.04)
broken/VisitorAdded.java:9: error: Fee is not abstract and does not override abstract method visitWallet(Wallet) in Visitor
static class Fee implements Visitor<Long> {
^
broken/VisitorAdded.java:13: error: Label is not abstract and does not override abstract method visitWallet(Wallet) in Visitor
static class Label implements Visitor<String> {
^
2 errors
$ javac broken/VisitorAdded.java (25.0.4.1+1-LTS)
broken/VisitorAdded.java:9: error: Fee is not abstract and does not override abstract method visitWallet(Wallet) in Visitor
static class Fee implements Visitor<Long> {
^
broken/VisitorAdded.java:13: error: Label is not abstract and does not override abstract method visitWallet(Wallet) in Visitor
static class Label implements Visitor<String> {
^
2 errors
$ javac broken/VisitorAdded.java (27+35)
broken/VisitorAdded.java:9: error: Fee is not abstract and does not override abstract method visitWallet(Wallet) in Visitor
static class Fee implements Visitor<Long> {
^
broken/VisitorAdded.java:13: error: Label is not abstract and does not override abstract method visitWallet(Wallet) in Visitor
static class Label implements Visitor<String> {
^
2 errors
+14
View File
@@ -0,0 +1,14 @@
$ java -cp v1+Hack.class Main (Hack was compiled against a Payment that permitted it) (21.0.10+7-Ubuntu-124.04)
loading Card: Card
loading Hack...
Exception in thread "main" java.lang.IncompatibleClassChangeError: class Hack cannot implement sealed interface Payment
$ java -cp v1+Hack.class Main (Hack was compiled against a Payment that permitted it) (25.0.4.1+1-LTS)
loading Card: Card
loading Hack...
Exception in thread "main" java.lang.IncompatibleClassChangeError: Failed listed permitted subclass check: class Hack is not a permitted subclass of Payment
$ java -cp v1+Hack.class Main (Hack was compiled against a Payment that permitted it) (27+35)
loading Card: Card
loading Hack...
Exception in thread "main" java.lang.IncompatibleClassChangeError: Failed listed permitted subclass check: class Hack is not a permitted subclass of Payment
+8
View File
@@ -0,0 +1,8 @@
$ javac -d out $(find multi/demo.pay -name '*.java') && java -p out -m demo.pay/pay.app.Main (21.0.10+7-Ubuntu-124.04)
card in module demo.pay
$ javac -d out $(find multi/demo.pay -name '*.java') && java -p out -m demo.pay/pay.app.Main (25.0.4.1+1-LTS)
card in module demo.pay
$ javac -d out $(find multi/demo.pay -name '*.java') && java -p out -m demo.pay/pay.app.Main (27+35)
card in module demo.pay