Method Design
Source: Effective Java, 3rd Edition — Chapter 8 by Joshua Bloch (Items 49–52)
This lesson covers method (and constructor) design: validate parameters, defensive copies, careful signatures, and safe overloading.
Item 49: Check parameters for validity
Detect bad inputs early — at the method entry — with clear exceptions. Silent wrong answers or corrupted object state are worse than a fast failure (failure atomicity, Item 76).
Objects.requireNonNull(x, "msg")— null checks (Java 7+); returnsxfor inline assignment.Objects.checkIndex/checkFromToIndex— array/list bounds (Java 9+).- Document with
@throwsin Javadoc for public/protected APIs. - Private helpers may use
assert(enabled with-ea) when callers are under your control.
Check parameters stored for later use even if unused immediately (factory storing an array reference). Copy before or after validation depending on TOCTOU — for mutable params, copy first, then validate copies (Item 50).
Skip explicit checks when validation is implicit in the computation (Collections.sort → ClassCastException), but beware wrong exception types (use exception translation, Item 73).
Item 50: Make defensive copies when needed
Even in a “safe” language, mutable components (Date, arrays, collections) let clients break invariants unless you defensive-copy on input and often on output.
Broken Period with Date fields: client mutates end after construction. Fix:
- Constructor:
new Date(start.getTime())— copy before validating copies (TOCTOU). - Accessors: return new copies, not internal references.
- Prefer
new Date(...)overclone()on nonfinal types (untrusted subclasses).
Prefer immutable components (Instant, LocalDateTime) when possible (Item 17). Arrays are always mutable — clone before return (Item 15). Document when callers may keep aliases (transfer-of-ownership APIs).
Defensive copies
1 / 2Period(Date start, Date end) validates then assigns this.start = start without copying
Pick an answer to see if you are right.
Item 51: Design method signatures carefully
Practical API hygiene:
- Names: clear, conventional (Item 68); match Java library style.
- Convenience methods: only when they pull their weight — avoid API bloat.
- Parameter lists: aim for ≤4 parameters; split methods, helper holder classes, or builder-style invokers for long lists.
- Parameter types: prefer interfaces over concrete classes (
MapnotHashMap, Item 64). - Booleans: prefer two-element enums when meaning isn’t obvious (
TemperatureScale.CELSIUSvstrue).
Item 52: Use overloading judiciously
Overloading is resolved at compile time from static parameter types. Overriding is resolved at runtime from the object’s type.
CollectionClassifier overloads classify(Set), classify(List), classify(Collection) — loop variable is Collection<?>, so all three calls hit classify(Collection) → "Unknown Collection".
Fix: one method with instanceof (or double dispatch):
return c instanceof Set ? "Set" :
c instanceof List ? "List" : "Unknown Collection";
Autoboxing trap: list.remove(i) with int i calls remove(int index), not remove(Integer) — Set.remove autoboxes to remove(E). Cast to Integer when you mean element removal.
Guidelines: avoid multiple overloads with the same arity when confusing; don’t overload on different functional interfaces in the same position; constructor overloads need radically different parameter types when shared arity.
Overloading vs overriding
1 / 2for (Collection<?> c : arrays) classify(c) with classify(Set), classify(List), classify(Collection)
Pick an answer to see if you are right.
Items 49–52 map
| Item | Move |
|---|---|
| 49 | Validate early; requireNonNull; document @throws |
| 50 | Defensive-copy mutable params and returns |
| 51 | Short lists; interface parameter types; enums over booleans |
| 52 | Overloading ≠ overriding; beware autoboxing overload traps |
Next: Varargs, Returns, and Documentation (Items 53–56).