Records, Sealed Classes & Modern Java Features
This is the chapter that closes the loop this whole track opened: Java exists specifically to retroactively supply the fluency Kotlin's own course always assumed, and Kotlin's most distinctive features — data classes, sealed classes — turn out to have direct Java counterparts, added relatively recently.
Records — Data Carriers Without the Boilerplate
One line generates a constructor, accessor methods (x()/y()), and correct equals()/hashCode()/toString() implementations — a direct, real payoff of java2-2's own equals()/hashCode() contract, generated for free instead of hand-written and risked getting wrong. A record's fields are always final — genuinely immutable by design, not merely by convention.
Records Are Genuinely Comparable to Kotlin's data class
Kotlin's own data class auto-generates the identical set of methods — equals(), hashCode(), toString(), plus a copy() Java's record doesn't directly provide. This is a real, direct parallel — the same underlying idea (a small, deliberately boilerplate-free data carrier), arriving in each language on its own schedule.
Sealed Classes/Interfaces
sealed combined with permits declares a genuinely closed, exhaustively-known set of subtypes — nothing outside the permits list can ever implement Shape. This is a direct parallel both to Kotlin's own sealed class material and to rust1-6's data-carrying enums, which achieve the same closed-set guarantee through a different mechanism entirely.
Exhaustive switch Pattern Matching Over Sealed Types
This is java1-3's light pattern-matching touch, finally paid off in full: because Shape is sealed, the compiler knows the complete, closed set of subtypes and can verify every one is handled — a missing case is a compile error, not a runtime surprise. Combining a closed type hierarchy with exhaustive matching is a genuine safety win neither piece delivers alone.
The Bridge Back to Kotlin
Kotlin's own sealed class plus a when expression is checked exhaustively the exact same way — the compiler refuses to compile a when over a sealed type that's missing a branch. This Java feature exists specifically to retroactively supply the concept Kotlin's own course used without ever building it from first principles — the loop this entire Java track was written to close.
| Feature | Kotlin | Rust (rust1-6) | Java |
|---|---|---|---|
| Boilerplate-free data carrier | data class | derive macros (rust3-4) | record |
| Closed set of subtypes | sealed class | a data-carrying enum | sealed interface/class + permits |
| Exhaustive matching | when (compiler-checked) | match (compiler-checked) | switch over a sealed type (compiler-checked) |
record replaces pages of hand-written constructor/accessor/equals/hashCode/toString boilerplate with one line, with no risk of the hand-written version drifting out of sync.
java.lang.Record already, and java1-5's single-inheritance rule means that slot is taken — a record can still implement any number of interfaces, but it can never extends a regular class of its own.
Coding Challenges
Write a record Person(String name, int age), create two instances with identical values, and print whether they're equal and what toString() produces — without writing any of those methods yourself.
📄 View solutionWrite a sealed interface Vehicle permitting exactly Car and Motorcycle (as records), then write a switch expression over a Vehicle that returns each type's wheel count, with no default branch, and explain why omitting a case would fail to compile.
📄 View solutionAttempt to write a record that extends a regular class. Show the resulting compile error and explain why in terms of java1-5's single-inheritance rule and what a record already implicitly extends.
📄 View solutionChapter 7 Quick Reference
- record generates a constructor, accessors, equals()/hashCode()/toString() automatically — fields are always final
- Genuinely comparable to Kotlin's own data class — same idea, different arrival schedule
- sealed + permits declares a closed, exhaustively-known set of subtypes, paralleling Kotlin's sealed class and rust1-6's data-carrying enums
- switch over a sealed type is compiler-verified exhaustive — a missing case is a compile error, no default needed
- A record can't extend a regular class — it already implicitly extends java.lang.Record, and java1-5's single-inheritance rule applies
- Next chapter: the capstone — a real Java project combining OOP, generics, collections, streams, and exception handling