Master Java API Design: 25 Essential Best‑Practice Rules from Effective Java
This article distills key guidelines from Effective Java, covering static factory methods, builder patterns, singleton protection, utility class design, memory‑leak avoidance, equals/hashCode contracts, composition over inheritance, generics, enums, exception handling, and many other best‑practice rules to help Java developers write cleaner, safer, and more maintainable code.
Effective Java is a classic must‑read; strictly following its principles dramatically improves API quality and coding standards.
1. Consider static factory methods instead of constructors
Example
Integer.valueOf("1"), Boolean.valueOf("true") and similar.
Advantages
High readability (method name conveys intent)
Performance (may avoid creating a new object)
High flexibility (can return subclasses)
These advantages are explained below.
High readability
new Point(x, y) vs Point.at(x, y) or Point.origin(); the latter are self‑describing.
Performance
Objects like Boolean can be pre‑instantiated and reused, avoiding repeated allocation.
<code>public final class Boolean implements Serializable, Comparable<Boolean> {
// pre‑created instances
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
private Boolean(boolean value) { this.value = value; }
private Boolean(String s) { this(parseBoolean(s)); }
public static Boolean valueOf(boolean b) { return b ? TRUE : FALSE; }
public static Boolean valueOf(String s) { return parseBoolean(s) ? TRUE : FALSE; }
// ... other code
}</code>High flexibility
Factory methods can return specific subclasses, enabling more powerful APIs such as Collections.
<code>public class Collections {
private Collections() {}
public static final List EMPTY_LIST = new EmptyList<>();
public static final <T> List<T> emptyList() { return (List<T>) EMPTY_LIST; }
private static class EmptyList<E> extends AbstractList<E> implements RandomAccess, Serializable { /* ... */ }
public static <E> List<E> checkedList(List<E> list, Class<E> type) {
return (list instanceof RandomAccess)
? new CheckedRandomAccessList<>(list, type)
: new CheckedList<>(list, type);
}
static class CheckedRandomAccessList<E> extends CheckedList<E> implements RandomAccess { /* ... */ }
static class CheckedList<E> extends CheckedCollection<E> implements List<E> { /* ... */ }
}</code>2. When a class needs many constructors, use a Builder
Multiple constructors make client code error‑prone, especially in Android UI components.
<code>public class AlertDialog {
private int width;
private int height;
private String title;
private String confirmText;
private String denyText;
private AlertDialog() {}
public AlertDialog(int w, int h) { this(w, h, null); }
public AlertDialog(int w, int h, String t) { this(w, h, t, "OK"); }
public AlertDialog(int w, int h, String t, String c) { this(w, h, t, c, null); }
public AlertDialog(int w, int h, String t, String c, String d) { /* set fields */ }
}
</code>Using a Builder isolates parameter setting and enables validation at construction time.
<code>public class AlertDialog {
private AlertDialog(Builder b) {
width = b.width;
height = b.height;
if (width == 0 || height == 0) throw new IllegalArgumentException("size must be set");
// ... other assignments
}
public static class Builder {
private int width;
private int height;
private String title;
private String confirmText;
private String denyText;
public Builder setTitle(String t) { this.title = t; return this; }
// other setters …
public AlertDialog build() { return new AlertDialog(this); }
}
}
</code>3. Strengthen Singleton with private constructor or enum
Reflection can break a classic singleton; using a private constructor or a single‑element enum prevents this.
<code>public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {}
}
</code> <code>public enum Elvis { INSTANCE; /* methods */ }
</code>4. Prevent instantiation of pure utility classes
Declare a private constructor that throws an exception.
<code>public class Util {
private Util() { throw new AssertionError(); }
}
</code>Note: this also blocks inheritance.
5. Avoid creating unnecessary objects
Reuse objects when possible.
Use object pools for expensive objects.
Do not pool cheap objects; modern JVMs handle them efficiently.
6. Eliminate obsolete object references
Memory leaks can arise from self‑managed memory, caches, and listeners.
Self‑managed memory: clear references after pop operations.
<code>public class Stack {
private Object[] elements = new Object[DEFAULT_INITIAL_CAPACITY];
private int size;
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object obj = elements[--size];
elements[size] = null; // allow GC
return obj;
}
// ... other methods
}
</code>Cache: use WeakHashMap or periodic cleanup.
Listeners/Callbacks: unregister them or store as weak references.
7. Do not invoke GC explicitly
Let the JVM manage garbage collection; explicit finalizer calls degrade performance.
8. Override equals following the contract
Reflexive
Symmetric
Transitive
Consistent
Non‑null
9. When overriding equals , also override hashCode
This ensures correct behavior in hash‑based collections.
10. Always override toString
Provide a clear, descriptive representation of the object.
11. Be cautious when overriding clone
Consider alternative copying strategies.
12. Implement Comparable when natural ordering makes sense
13. Minimize accessibility of classes and members
Prefer private > protected > package‑private > public . Avoid widening visibility without justification.
14. Use accessor methods instead of public fields
15. Minimize mutability
16. Prefer composition over inheritance
Composition reduces coupling and avoids inheritance‑related fragility.
17. Design for inheritance or forbid it
18. Prefer interfaces to abstract classes
19. Interfaces should only define types
20. Class hierarchies are preferred over marker interfaces
21. Represent strategies with function objects
Pass listener‑like objects; reuse them instead of creating new anonymous instances.
22. Prefer static class members when appropriate
Nested static classes avoid implicit references to the outer instance.
23. Specify generic types instead of raw types
Use List<String> rather than raw List .
24. Eliminate non‑first‑check warnings
Address IDE warnings; avoid unnecessary @SuppressWarnings annotations.
25. Prefer List over arrays
Lists provide type safety and avoid runtime ArrayStoreException .
26‑28. Prefer generics and bounded wildcards (PECS)
<code>public void pushAll(Iterator<? extends E> src) { for (E e : src) push(e); }
public void popAll(Collection<? super E> dst) { while (!isEmpty()) dst.add(pop()); }
</code>29‑35. Use enums, EnumSet, EnumMap, annotations, and @Override wherever applicable
<code>public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }
public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }
Set<Style> styles = EnumSet.of(Style.BOLD, Style.ITALIC);
</code>38. Validate method parameters
Public methods should throw exceptions for illegal arguments; private methods may use assertions.
39. Perform defensive copying
<code>public Period(Date start, Date end) {
this.start = new Date(start);
this.end = new Date(end);
if (this.start.compareTo(this.end) > 0) throw new IllegalArgumentException();
}
public Date getStart() { return new Date(start); }
public Date getEnd() { return new Date(end); }
</code>40‑43. Design method signatures carefully, avoid overload abuse, limit varargs, return empty collections instead of null
44. Document all exported API elements with Javadoc
45‑46. Minimize variable scope and prefer foreach loops
48. Avoid float / double for precise calculations; use BigDecimal
<code>System.out.println(new BigDecimal("1.03").subtract(new BigDecimal("0.42")));
</code>49. Prefer primitive types over boxed types
50. Use proper types instead of String for numbers, booleans, enums, etc.
51. Use StringBuilder for intensive string concatenation
52‑53. Program to interfaces and avoid reflection unless necessary
54‑55. Use JNI and optimizations sparingly
56‑58. Follow naming conventions, use exceptions appropriately, and distinguish checked vs runtime exceptions
(完)
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.