📋 Copy Java OOP Encapsulated Class & Record Pattern
import java.util.Objects;
// Modern Immutable Java Record (JDK 16+)
public record Player(String username, int level, double health) {
public Player {
Objects.requireNonNull(username, "Username cannot be null");
if (level < 1) throw new IllegalArgumentException("Level must be >= 1");
}
}
// Classical Encapsulated OOP Blueprint
public class Weapon {
private final String id;
private int durability;
public Weapon(String id, int durability) {
this.id = Objects.requireNonNull(id, "Weapon ID cannot be null");
this.durability = durability;
}
public void use() {
if (durability > 0) durability--;
}
public int getDurability() { return durability; }
public String getId() { return id; }
}
⚠️ 5 Fatal Traps & Engineering Pitfalls
Trap #1: Overriding equals() Without Overriding hashCode()
If two objects are considered equal via equals(), they MUST return the exact same hashCode() integer. Overriding equals() alone causes objects to vanish inside HashSet or become completely unretrievable from HashMap keys.
Trap #2: Leaking Mutable Internal State Through Getters
Returning a direct reference to a mutable field (e.g., public List<Item> getInventory() { return this.items; }) allows external callers to modify the private state without validation. Always return defensive copies or unmodifiable wrappers like Collections.unmodifiableList(items).
Invoking a non-private, non-final method inside a superclass constructor can trigger a subclass method override before the subclass fields have been initialized. This leads to subtle, hard-to-trace NullPointerExceptions during instantiation.
Trap #4: The Fragile Base Class & Deep Inheritance Hell
Creating inheritance chains deeper than 2 levels tightly couples child classes to parent implementation details. A change in a base class method breaks behavior across dozens of subclasses. Prefer object composition and interfaces over class inheritance.
Trap #5: Anemic Domain Models (Getters and Setters Everywhere)
Creating classes with only private fields and automatic getters/setters for every field turns classes into dumb data containers, violating encapsulation. Business rules, state validations, and mutations should be encapsulated directly within domain methods.
💬 Frequently Asked Questions
What is the contract between equals() and hashCode() in Java?
If two objects evaluate to true via equals(), their hashCode() values must be identical. If hashCode() differs, they are guaranteed not equal. However, two unequal objects may share the same hash code (a hash collision).
Why is composition generally favored over inheritance in modern Java?
Composition provides loose coupling, allows changing behavior dynamically at runtime by swapping component implementations, and avoids exposing subclass internals to fragile base class changes.
What are Java Records and when should they be used instead of regular classes?
Records (introduced in Java 16) are concise immutable data carriers. The compiler automatically generates constructor, getters, equals(), hashCode(), and toString() methods, eliminating boilerplate code.
What is the difference between an Interface and an Abstract Class in Java?
An abstract class can maintain mutable instance state, constructors, and default implementations (single inheritance). An interface defines a contract or capability (multiple inheritance) and can only declare public static final constants and default/static methods.
Why is invoking polymorphic methods from within a constructor dangerous?
When a superclass constructor runs, the subclass instance fields have not yet been initialized. If the constructor calls a method overridden by the subclass, that method executes against null/uninitialized subclass state.