Chapter 3 Flashcards — Classes and Interfaces
flashcards effective-java classes-and-interfaces
What are the four Java access modifiers in order from most to least restrictive?
?
private (class only) → package-private/default (same package) → protected (subclasses + same package) → public (everywhere). The goal is to start at private and widen only when there is a documented reason.
Why is public static final String[] ROLES always a bug, even though the reference is final?
?
The final keyword only prevents reassigning the field to a different array. The array’s contents are still mutable — any caller can do ROLES[0] = "HACKER". Fix: use Collections.unmodifiableList(Arrays.asList(ROLES)) or List.of(...) (Java 9+) and keep the underlying array private.
What is the single rule for public classes regarding fields and accessors (Item 16)?
?
Public classes should never expose mutable fields directly. Expose state via accessor methods (getters). This allows changing the representation, enforcing invariants, and adding notifications later without breaking clients. Exception: package-private and private nested classes may expose fields directly.
What are the five rules for making a class immutable (Item 17)?
?
- No mutating methods (no setters).
- Cannot be extended (declare
final, or use a private constructor with static factories). - All fields are
final. - All fields are
private. - Exclusive access to mutable components — make defensive copies in constructors and accessors for any mutable field references.
What is the “wither” pattern used for in immutable classes?
?
A wither method (also called a “with” method) returns a new instance of the immutable class with one field changed, rather than modifying the current instance. Example: period.withStart(newStart) returns a new Period with newStart but the same end. This is how immutable classes provide “modification” without mutability.
What does Java 16 records give you automatically that satisfies Items 15, 16, and 17?
?
Records automatically provide: private final fields, canonical constructor, accessor methods (named after components, e.g., x() not getX()), equals(), hashCode(), and toString(). Fields cannot be widened beyond their component visibility, the class is implicitly final, and no instance fields can be added outside components — all five immutability rules are satisfied automatically.
What is the fragile base class problem? Give one concrete example.
?
A subclass depends on implementation details of its superclass. When the superclass changes, the subclass breaks without any change to its own code. Example: InstrumentedHashSet overrides both add() and addAll() to count insertions. But HashSet.addAll() internally calls add(), so addAll(List.of("a","b","c")) increments the counter by 6 (3 from addAll + 3 from add), not 3. The fix is composition — wrap the HashSet instead of extending it.
What is the Decorator (wrapper) pattern and how does it solve the fragile base class problem?
?
The Decorator pattern uses composition: the wrapper class holds a reference to the wrapped object (private final Set<E> s) and implements the same interface by forwarding all calls to s. Added behavior is implemented in the wrapper’s overrides, but those overrides delegate to the wrapped object — not to super. This means there is no dependency on the wrapped class’s internal self-use, so changes to HashSet’s implementation cannot break the wrapper.
What must you NEVER do in a constructor of a class designed for inheritance (Item 19)?
?
Never call an overridable method from a constructor. If a subclass overrides the method, it will be called before the subclass’s constructor runs, meaning the subclass’s fields are still null/uninitialized. This is a source of NullPointerException and subtle initialization bugs. The same rule applies to clone() and readObject() in classes implementing Cloneable/Serializable.
What is the @implSpec Javadoc tag used for?
?
@implSpec documents the self-use of a method — which other methods it calls internally. This is required for any overridable method in a class designed for inheritance, so subclasses know what they are overriding and can maintain the contract correctly. Without this documentation, any override could break when the superclass changes its internal call chain.
What is the key advantage of interfaces over abstract classes (Item 20)?
?
Interfaces allow mixins — a class can implement any number of interfaces but can only extend one abstract class. This means a class can declare that it implements Singer, Songwriter, and Comparable simultaneously. Abstract classes force a strict single-inheritance hierarchy; adding capabilities via abstract classes requires combinatorial explosion of abstract classes (e.g., AbstractSingerSongwriter).
What is a skeletal implementation class and how does it work?
?
A skeletal implementation is an abstract class (named AbstractXxx) that implements an interface by providing default implementations for most methods, leaving only the primitive/essential ones abstract. This lets classes that don’t extend anything get the benefit of the default implementations simply by delegating to an inner private instance of the skeletal class (simulated multiple inheritance). Example: AbstractList provides a complete List based on just get(int) and size().
What was the problem with Java 8 adding removeIf() as a default method to Collection?
?
java.util.Collections.synchronizedCollection() returns a wrapper that synchronizes all operations on a mutex lock. The default removeIf() implementation doesn’t know about this lock and does NOT acquire it, allowing concurrent modification of the collection. This shows that default methods can silently break existing correct implementations — the contract of SynchronizedCollection (all ops synchronized) was violated without any code change in that class.
What is the constant interface antipattern and why is it harmful?
?
Implementing an interface solely to inherit its static final constants (e.g., implements PhysicalConstants). It is harmful because: (1) it leaks implementation details into the public API — the class now permanently “is-a” PhysicalConstants; (2) it pollutes subclass namespaces; (3) interfaces represent types — instanceof PhysicalConstants is meaningless. Fix: use a utility class with a private constructor and static import.
What is a tagged class and why is it bad?
?
A tagged class uses a discriminator field (the “tag”) to indicate which variant an object represents, with irrelevant fields for other variants and switch statements everywhere. Problems: (1) clutter — irrelevant fields allocated for every instance; (2) error-prone — adding a new variant requires finding every switch; (3) no type safety — wrong fields can be accessed; (4) memory waste — every instance carries fields for all variants. Replace with a class hierarchy.
How do sealed classes (Java 17) improve on the plain class hierarchy pattern for Item 23?
?
A plain class hierarchy is open — anyone can add a new subclass, making exhaustive switch statements impossible without a default case. Sealed classes declare exactly which subclasses are permitted (permits Circle, Rectangle). The compiler knows the complete set, so pattern-matching switch (Java 21) can verify exhaustiveness — no default needed. Adding a new permitted subclass causes a compile error in any non-exhaustive switch, forcing the programmer to handle it.
What is the difference between a nonstatic member class and a static member class?
?
A nonstatic member class holds a hidden reference to its enclosing instance (Outer.this). It can access the enclosing instance’s fields and methods but cannot be instantiated without an enclosing instance. A static member class has no such reference — it is just a regular class that happens to be nested inside another class for organizational purposes. Static member classes are preferred unless access to the enclosing instance is genuinely needed.
Why can a nonstatic inner class cause a memory leak?
?
The inner class instance holds a hidden reference to the outer class instance. If the inner class instance is long-lived (e.g., stored in a collection or passed to a long-running task), it prevents the outer instance from being garbage collected, even if nothing else references the outer instance. The GC sees the outer instance as reachable via the inner instance’s hidden field. Making the nested class static eliminates this reference.
What is the rule for anonymous classes in modern Java?
?
Anonymous classes should almost always be replaced by lambdas (for functional interfaces) or method references. Anonymous classes are still necessary when you need a non-functional interface (multiple methods), need a class that extends a class (not just an interface), or need to define multiple methods. For simple callbacks and event handlers, lambdas are shorter and clearer.
What is the Java rule about multiple top-level classes in a single source file?
?
Java permits multiple top-level classes in one file but this is always wrong. Only one class can be public (and it must match the filename), but non-public top-level classes are allowed — and their definitions can conflict with separately-compiled files of the same name. Compilation output depends on the order files are passed to the compiler, making behavior non-deterministic. One top-level class per source file, always.
How do you make a class effectively immutable without declaring it final?
?
Use a private constructor combined with static factory methods. Since the constructor is private, external code cannot create subclasses (a subclass constructor must call super(), which requires access to the constructor). Static factories can return instances of the class or (internally) optimized subclasses. This approach is used by BigInteger and BigDecimal (though they were not implemented this way initially — a historical mistake).
What does the principle of least privilege mean in Java class design?
?
Every class and member should be as inaccessible as possible. Start every class as package-private and every member as private. Widen access only when there is a clear, documented need. This isolates implementation details, allows the internals to change freely without breaking clients, and prevents accidental misuse of internal API.
What is the difference between List.copyOf() and Collections.unmodifiableList() for defensive copies?
?
Collections.unmodifiableList(list) creates an unmodifiable view — modifications to the original backing list are still visible through the view. List.copyOf(list) (Java 10+) creates a true defensive copy — a snapshot that is completely independent of the original. For defensive returns in immutable classes, List.copyOf() is safer.
What Java 17 feature allows an interface to restrict which classes can implement it?
?
Sealed interfaces (sealed interface Foo permits Bar, Baz). The compiler enforces that only the listed classes/interfaces can implement the sealed interface (within the same module or package). Each permitted implementor must be final, sealed, or non-sealed. This gives the interface designer formal control over the subtype hierarchy.
Why are records implicitly static when declared inside another class?
?
Records are intended as pure data carriers with no mutable state. A nonstatic nested class holding a hidden reference to an outer instance would mean the record carries hidden mutable state (the outer instance), contradicting its purpose. Java therefore makes nested records implicitly static, consistent with Item 24’s rule that nested classes should be static unless they genuinely need the outer instance.
What is the protected access modifier’s relationship to inheritance (Item 15)?
?
protected members are accessible to subclasses — they form part of the exported API for inheritance. Once you make something protected, any subclass in any package can access it, meaning it is effectively public for subclasses. This is why the advice “minimize accessibility” specifically warns about protected: it is far less restrictive than it looks, and removing a protected member after publication is a breaking change.
When is it appropriate to use inheritance rather than composition?
?
Inheritance is appropriate when: (1) There is a genuine is-a relationship (not just a can-do relationship); (2) the superclass is designed and documented for inheritance; (3) you control both classes or the superclass is explicitly designed for subclassing; (4) the subclass will truly benefit from every method in the superclass’s API. If you’re unsure, ask: “Is every instance of subclass also an instance of superclass in a meaningful sense?” If not, use composition.
What is the non-sealed modifier in Java 17 sealed hierarchies?
?
non-sealed allows a permitted subclass to re-open the hierarchy — any class can extend a non-sealed class even if the parent is sealed. This is an explicit opt-out: the author of the sealed class is saying “this particular branch of the hierarchy is open.” It must be explicitly written — you cannot accidentally create an open branch in a sealed hierarchy.
How does java.util.Collections.unmodifiableSet() relate to Item 15?
?
It is a way to expose a read-only view of a private mutable set as public API. The field itself stays private and mutable (so the class can update it), while callers get an unmodifiable view that prevents them from corrupting the state. This is a practical application of Item 15 — exposing the minimum access necessary (read, but not write) without giving up the ability to evolve the internal representation.
What is a compact constructor in a Java record, and when do you use it?
?
A compact constructor omits the parameter list — it implicitly has the same parameters as the canonical constructor. It is used to add validation and normalization logic. The compiler automatically assigns the parameters to the corresponding fields after the compact constructor body runs. Example: public Range { if (min > max) throw new IllegalArgumentException(); }. You cannot explicitly assign to components inside a compact constructor — the assignment happens automatically after.
Total Cards: 30
Review Time: ~30 minutes
Priority: HIGH
Last Updated: 2026-05-10