--- title: Modern and Practical Oops collection: Java Oops author: Suraj Kumar Jha updated: 2026-09-29 source: https://docs.chaicode.com/java-oops/modern-and-practical-oops --- # Modern and Practical Oops ![A roadmap with five milestones labelled Interfaces, Enums, Records, Sealed, Immutability](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/ff24c6575203dd6c5c3fef07ecc1758c860ba4e196de8d6b9b7521c5d3846a80.png) Phase 4: Modern Practical OOP — Five topics in this phase: • Interfaces (deep dive) • Enums, Records, Sealed Classes • Immutable objects & good design habits ![A contract document labelled 'Payment' with arrows to UPI, Card, Cash signing it](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/e681de0b613698f021aff3f2eb29a21fa5d6d1f36cbee35cd019f014ecb25105.png) Interface = A Contract — An interface lists behaviours a class MUST provide. Interface says WHAT should happen; the class decides HOW. Example: interface Payment { void pay(double amount); } class UPI implements Payment must write its own pay(). ![Empty method box in interface, filled-in box in UPI class below it](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/e56602a28f983c199d09d3f5aecae1c5d818fdc0c80099c2433cffc8984527c2.png) Abstract Methods — Declared in an interface with no body: void pay(double amount); Java silently treats it as public abstract. Every implementing class must fill in the body. Interface = what; Class = how. ![Interface handing a ready-made 'receipt' tool to UPI, while Card holds its own custom version](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/aea2c94c6e288b5ff96e0e4c9188377ac91cba3ca739f3dda189badae3afe8b2.png) Default Methods (Java 8) — A method inside an interface that already has a body. Implementing classes get it for free, e.g. UPI can call generateReceipt(). A class can still override it, like Card does with its own receipt. ![Many class boxes; red cracks for 'new abstract method' vs green checks for 'default method'](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/a85e351b7faae9d0577ba885ad48043e051fe6b8d4d1e5840827945d05cfd7cc.png) Why Default Methods Exist — Imagine thousands of classes implement Payment. Adding a new abstract method would break all of them. A default method adds new behaviour without breaking old code. ![Payment interface with a toolbox attached; UPI class reaching for it and blocked by a cross](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/feac107ec41b20a145b0b35d4c61c16ff0503bccf4bbd6bb435ba053c536f48f.png) Static Methods in Interfaces — Belong to the interface itself, not objects. Call as Payment.paymentInfo() ✓ UPI.paymentInfo() ✗ — not inherited by classes. Use them for interface-level utilities. ![Interface box with a locked inner room 'logTransaction' used only by generateReceipt](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/8989a9370e2a50bc5b32b04c6f117ac5ed51e8320946729d072645a4cdb2b9a9.png) Private Methods (Java 9) — Internal helpers shared by methods inside the interface. Example: generateReceipt() calls private logTransaction(). Outside code cannot call it: upi.logTransaction() ✗ ![A stone tablet engraved 'PLATFORM_FEE = 2.0' with a padlock](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/501ce30d64d1880e754cb19a00a8a1dce95b6c7347f49d70bcb9e6721525d479.png) Interface Constants — Variables in an interface are automatically public static final. double PLATFORM_FEE = 2.0; Access via Payment.PLATFORM_FEE. Cannot change it: Payment.PLATFORM_FEE = 10 ✗ ![UPI class with two plugs connecting into Payment and Refundable sockets](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/ec1df9acab88a57e79fc85781e9f55e02846f406f38c1fd53e80776b5131b697.png) Multiple Interfaces — A class cannot extend two classes: class C extends A, B ✗ But it can implement many interfaces. Example: class UPI implements Payment, Refundable — so it can pay() and refund(). ![Two arrows labelled A.show() and B.show() colliding at Demo, with a referee resolving them](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/424167d399e03b88a14be8677ae86f542f506acb742a3b6a4d2757ea7813b4a4.png) Default Method Conflict — Interfaces A and B both have default show(). Class Demo implements A, B must override show(). Inside, pick versions using A.super.show() and B.super.show(). ![Calculator interface with one slot, a lambda '(a,b) -> a+b' plugging into it](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/981c9373025d477e4a226254201aa0a89f189d4b1d6f7613acad690a8f5d54d7.png) Functional Interfaces — An interface with exactly one abstract method, marked @FunctionalInterface. Default methods are still allowed. Can be written as a lambda: Calculator add = (a, b) -> a + b; ![Four small machines: a yes/no gate, a converter, a sink, and a dispenser](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/9fd135f090f1bd3f07eb150d602768c44c0db9396863e3e9c5ba06f20d818511.png) Built-in Functional Interfaces — Predicate: tests a condition → isEven.test(10) Function: converts input to output → getLength.apply("Java") Consumer: takes input, returns nothing → prints a message Supplier: takes nothing, gives a value → getGreeting.get() ![A weekday dial that can only point to the seven listed days](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/bc6550839d41676b61af09302694670ddb4eef3e72ab2bdf44d19613a0af067d.png) Enum = Fixed Set of Options — A special type for a fixed list of constants. enum Day { MONDAY, TUESDAY, ... SUNDAY } Day today = Day.MONDAY; today can only hold one of those values. ![A free-text box full of typos vs a clean dropdown with three options](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/3d7083dd8f09c9895329e5e154eff8efc7a4878b6f810bb38c1b2dac52e4df8b.png) Why Enums Beat Strings — String status = "SUCCESS" allows typos and junk values. enum Status { PENDING, SUCCESS, FAILED } gives type safety. Status.INVALID ✗ — the compiler rejects it. ![Three badges LOW=1, MEDIUM=2, HIGH=3 each carrying a small data tag](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/8ed02e3a8fcfd2a5633dc35e3e2652ad176a4001f6ba6c07f1b5b3ad53f5c3d6.png) Enums with Fields & Methods — Enums can have fields, constructors and methods like classes. LOW(1), MEDIUM(2), HIGH(3) each store a value. Level.HIGH.getValue() prints 3. Enum constructors are implicitly private. ![Three labelled buttons: values(), valueOf(), name() with sample outputs](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/f8bafc412af134c97114ab33b935c8fd2a2bea7d655b1ac2d8571209109b292e.png) Handy Enum Methods — values() → all constants, useful in loops. valueOf("MONDAY") → string to enum constant. name() → the constant's name, e.g. MONDAY. ![Tree: Status at top with three object nodes PENDING, SUCCESS, FAILED](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/3f242e35392d423fb48b8c948e4a4142bd76b4b5874c308b3284fbf3fef44bf3.png) Enums Under the Hood — Not just strings — each constant is a real object. PENDING, SUCCESS, FAILED are predefined Status instances. You can't do new Status() ✗ — Java creates and manages them. Uses: Role, Day, OrderState (CREATED, SHIPPED, DELIVERED). ![A long traditional class shrinking into a one-line record](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/ab02ebf82aa2fc4fb559527a8d92ee37a0bab21c8ef575a4b25d8f0709612f75.png) Records: Data With Less Code — A special class for storing immutable data. record User(String name, int age) { } Java auto-generates constructor, accessors, equals(), hashCode(), toString(). Great for DTOs, API responses, config data. ![A User card showing name: Rahul, age: 20 with accessor arrows](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/a9083874b9bdaa32c73420b98f5ccdf302499e975ee48d35e40e27ad77c39c79.png) Using a Record — User user = new User("Rahul", 20); Read values with user.name() and user.age(). Note: name(), not getName(). Can add custom methods, e.g. isAdult() returns age >= 18. ![Sealed box labelled Rahul, a new box labelled Aman beside it, and a list peeking out of a crack](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/f17f734a3b9e139de8fc9f14b2dc6502b0fcc36bb97d1ab2ca571f0ff58e1af3.png) Records Are Immutable (Shallowly) — No setters; user.name = "Aman" ✗ Need a change? Create a new record: new User("Aman", 20). Caution: a mutable field like List roles can still be modified inside. ![A security gate checking 'age < 0?' before letting data into the record](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/8038eb0fc707bc5246c069bcf2f3fc091f4ffc05fca53bac1dc8c0d00d0ba8f8.png) Compact Constructor — Used mainly for validation. User { if (age < 0) throw new IllegalArgumentException(...); } No need to write this.name = name — the record assigns fields itself. ![Checklist with ✓ for interfaces/static and ✗ for extends/extra fields](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/416e041ff973010583ed73b827b2e6875c22629254c716a3586c9ac1e75ed2b9.png) Record Rules — Implicitly final; cannot extend another class. No extra instance fields beyond its components. Can have static fields/methods and implement interfaces. Record = concise, data-focused class with built-in value behaviour. ![Animal with a guest list letting in Dog and Cat, Horse stopped at the door](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/078f0fb9414ffd6827436f1a3b0391c46a20a19680fee83693dc1ac0876d1f9a.png) Sealed Classes: Controlled Inheritance — Normally any accessible class can extend Animal. sealed class Animal permits Dog, Cat { } Now only Dog and Cat may extend it. class Horse extends Animal → compile error. ![Hierarchy: Animal → Dog (final) and Cat (non-sealed) → PersianCat](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/0d6d3d4bf492c946e9f218d82dc0de117a015d297f017f9325aee2c38e6a9af4.png) Permitted Subclass Choices — Each permitted subclass must declare one: • final — nobody can extend it further • sealed — keep restricting (Cat permits PersianCat) • non-sealed — opens the branch to anyone ![Payment splitting into exactly two labelled paths: Card and UPI](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/d712fc2f223418293c5762c4d59b7db6bc141529a0fade701695e67f1c3561f4.png) Why Sealed + Pattern Matching — You know exactly which types exist, e.g. sealed interface Payment permits Card, UPI. Compiler knows all options when you check instanceof Card / UPI. Useful for domain models, state machines, API responses, ASTs. ![A frozen ice-block object labelled Rahul, 20 beside a fresh one labelled Rahul, 25](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/fe8ab0d23464e635f68c1c1f453f02f273b95912786867c21c28130234961c69.png) Immutable Objects — State cannot change after creation. User user = new User("Rahul", 20); — no setAge() ✗ Need a new state? Create a new object: new User("Rahul", 25). Known examples: String, Integer, Long, Double, LocalDate. ![A seven-step checklist next to a locked safe](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/7e2b7516bf991248fe5aa82650611baf47d8c036811f29053d3ec77fc96a3ef0.png) Making a Class Immutable — Class final; fields private and final. Set all fields in the constructor; no setters. Don't expose mutable internals; use defensive copies. Benefits: safer sharing, fewer accidental changes, good for concurrency and hash keys. ![Original list being photocopied; the object keeps the copy in a locked drawer](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/c04620b94ea1cc8246358dfb6640c87383441efaedaa7712bab405979640942f.png) Defensive Copies — Storing a caller's List directly lets them still change it. Fix: this.roles = List.copyOf(roles); Now outside references can't modify the object's internal list. ![A bank account object with a teller window instead of an open vault](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/5ec57ebb5ef4abda210b9efd241de896453b0227d9f4dbf923fdba99f5144bb1.png) Good OOP Habits (1) — Keep fields private. Let objects guard their state: account.withdraw(500), not account.balance = -500. Prefer immutability when state needn't change. Keep each class focused on one clear job. ![A deep tower Animal→Mammal→Dog→SpecialDog vs objects snapped together like Lego](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/a6cfec26bf25da0772560a0da60a5852248b4854b6af22c2452ddabee5019fc8.png) Good OOP Habits (2) — Program to interfaces: List names = new ArrayList<>(); Prefer composition for has-a relationships over deep inheritance. Don't auto-generate setters — ask: should this change from outside? ![A staircase of principles leading up to a trophy labelled 'Clean OOP Design'](https://docs.chaicode.com/cdn-cgi/image/width=1600,format=auto,fit=scale-down/assets/48d8f855d80e398f8fbf6b6ab1dc2c6be623bc56c6d5e968f84b6734257cc50b.png) The Big Picture — Encapsulation → hide state → control changes → prefer immutability → focused responsibilities → composition. Good OOP isn't about more classes. It's about controlling state, responsibilities and relationships clearly.