Chapter 1 Flashcards — Creating and Destroying Objects
flashcards effective-java object-lifecycle constructors builders singletons java
What are the five main advantages of static factory methods over constructors?
?
- Named — descriptive names convey intent (e.g.,
BigInteger.probablePrime). - Instance control — can return a cached or shared instance instead of always allocating.
- Return subtype — can return any subtype; implementation class can be hidden.
- Input flexibility — can accept parameters that the constructor type cannot.
- No new object requirement — can return an existing object (flyweight, singleton).
What is the main disadvantage of static factory methods?
?
Classes without public or protected constructors cannot be subclassed. Also, static factories are harder to discover in Javadoc because they do not stand out the way constructors do. Mitigate via naming conventions (of, from, valueOf, newInstance, getInstance).
What does the static factory naming convention from mean? Give an example.
?
from — a type-conversion method that takes a single parameter and returns a corresponding instance of this type. Example: Date.from(instant).
What does the static factory naming convention of mean? Give an example.
?
of — an aggregation method that takes multiple parameters and returns an instance that incorporates them. Example: EnumSet.of(JACK, QUEEN, KING) or List.of("a", "b", "c").
What does the static factory naming convention valueOf mean? Give an example.
?
valueOf — a more verbose alternative to from and of. Example: BigInteger.valueOf(long), Boolean.valueOf(true). Often used when converting from a primitive or string to the object type.
What is the telescoping constructor anti-pattern? Why is it bad?
?
Providing a series of constructors, each adding one more optional parameter. Bad because: it does not scale (n optional params → 2^n constructors in the worst case), call sites are unreadable (which positional int is which?), and it is error-prone when two adjacent parameters have the same type.
What is the JavaBeans pattern and why is it harmful?
?
Setting fields via setter methods after calling a no-arg constructor. Harmful because: the object is in an inconsistent state during construction, making it thread-unsafe; the class cannot be made immutable; invariants cannot be enforced atomically.
How does the Builder pattern fix the problems of telescoping constructors and JavaBeans?
?
- Construction is a single atomic step —
build()validates invariants before the object is created. - Named “parameters” via method chaining make call sites self-documenting.
- The result can be immutable — all fields are
final. - Scales cleanly with optional parameters and class hierarchies.
How do you implement a Builder for a class hierarchy to avoid unchecked casts?
?
Use the recursive type parameter / simulated self-type idiom:
abstract static class Builder<T extends Builder<T>> {
public T addTopping(Topping t) { ...; return self(); }
protected abstract T self(); // subclass returns `this`
}
class NyPizza extends Pizza {
static class Builder extends Pizza.Builder<Builder> {
protected Builder self() { return this; }
}
}This gives covariant return — NyPizza.Builder methods return NyPizza.Builder, not Pizza.Builder.
What are the three common approaches to implementing a singleton in Java?
?
- Public static final field:
public static final Elvis INSTANCE = new Elvis(); - Static factory:
private static final Elvis INSTANCE = new Elvis(); public static Elvis getInstance() { return INSTANCE; } - Single-element enum (preferred):
public enum Elvis { INSTANCE; }
Why is the single-element enum the best singleton implementation?
?
- The JVM guarantees one instance per enum constant per classloader — no extra code needed.
- Reflection-proof:
Constructor.newInstance()on an enum throwsIllegalArgumentException. - Serialization-proof: enum constants are deserialized by name back to the existing constant — no
readResolve()needed. - Concise and authoritative (Bloch’s recommendation).
When can you NOT use an enum singleton?
?
When the singleton must extend a non-Enum superclass — enums implicitly extend java.lang.Enum and cannot extend another class. In this case, use the static factory singleton and add readResolve() for serialization safety and a reflective guard in the constructor.
How do you make a singleton safe against serialization attacks?
?
Add a readResolve() method that returns the existing instance:
private Object readResolve() {
return INSTANCE; // return the canonical instance; GC handles new object
}Without this, deserialization creates a new instance of the singleton class.
How do you prevent instantiation of a utility class?
?
Add a private constructor that throws AssertionError:
private MathUtils() {
throw new AssertionError("Utility class — do not instantiate");
}The AssertionError guards against accidental calls from within the class. Making the class final further prevents subclassing.
Why is using abstract as a way to prevent instantiation of a utility class wrong?
?
An abstract class can be subclassed, and the subclass can be instantiated. It also sends the wrong design message — abstract implies “designed for subclassing”, which is not the intent of a utility class.
What is dependency injection (DI) and why is it preferred over hardwired resources?
?
Dependency injection means passing resources into a class (via constructor, method, or field) rather than having the class create or look up resources itself. Preferred because:
- Classes are decoupled from resource implementations.
- Unit testing is easy — inject mocks/stubs.
- Supports multiple configurations — different callers can inject different implementations.
- Thread-safe — each caller controls its own resource instance.
What is the factory overload of dependency injection? When is it useful?
?
Passing a Supplier<T> instead of a T directly:
public SpellChecker(Supplier<? extends Lexicon> dictionaryFactory) {
this.dictionary = dictionaryFactory.get();
}Useful when: the resource is expensive and should be created lazily, when a different instance is needed per operation, or when the client controls creation details (e.g., seeded random, dated config).
What is the performance cost of String.matches() and how do you fix it?
?
String.matches() compiles a new Pattern object on every call — regex compilation is expensive. Fix: compile the Pattern once as a static final field:
private static final Pattern ROMAN = Pattern.compile("^(?=.)M*...$");
static boolean isRomanNumeral(String s) { return ROMAN.matcher(s).matches(); }What is autoboxing, and how can it cause a performance problem?
?
Autoboxing silently converts primitives to their boxed equivalents (e.g., int → Integer). Problem: each conversion (outside the cache range) creates a new object on the heap.
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) sum += i; // ~2 billion Long objects!Fix: change Long sum to long sum. Prefer primitives over boxed primitives wherever possible.
What is new String("bikini") wrong? What should you write instead?
?
new String("bikini") creates a new String object that wraps the existing string literal "bikini" — the argument is already a String. Write String s = "bikini"; — the literal is interned by the JVM and reused across all callers.
What are the three most common sources of memory leaks in Java?
?
- Self-managed memory — a class holds an object array but does not null out elements when they are logically removed (e.g., a stack that does not null out popped slots).
- Unbounded caches — objects are put in a cache but never evicted; the cache grows without bound.
- Listeners and callbacks — code registers a callback but never deregisters it; the callback object is kept alive by the registry.
How do you fix the memory leak in a hand-rolled stack?
?
Null out the reference when the slot becomes logically empty:
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // eliminate obsolete reference
return result;
}What Java collection type is appropriate for a cache whose entries should be collected when the key is no longer in use?
?
java.util.WeakHashMap — it stores keys as weak references. When the key object has no other strong references, the GC can collect it and the WeakHashMap entry is automatically removed. Useful when the cache entry’s useful lifetime is determined by the lifetime of the key.
What are the problems with using finalizers (finalize()) in Java?
?
- No timing guarantee — JVM does not promise when (or if) finalizers run.
- Performance cost — finalized objects require two GC cycles to collect.
- Exceptions suppressed — exceptions thrown in
finalize()are silently ignored with no stack trace. - Finalizer attack — a subclass can override
finalize()to resurrect a partially-constructed object, bypassing constructor validation. - Finalizer thread starvation — if finalizers run slowly, objects accumulate, potentially causing
OutOfMemoryError.
What is the finalizer attack and how do you defend against it?
?
A malicious subclass overrides finalize() so that when the superclass constructor throws (rejecting invalid input), the subclass’s finalizer runs and can access the partially-constructed object — even saving a reference to resurrect it. Defense: make the class final (cannot be subclassed), or declare a final void finalize() that does nothing in the superclass.
What should you use instead of finalizers for resource cleanup?
?
Make the class implement AutoCloseable and provide a close() method. Clients use try-with-resources:
try (MyResource r = new MyResource()) {
r.doWork();
} // close() called automatically — deterministic, exception-preservingOptionally, add a Cleaner-based safety net to log a warning if close() was not called.
What is java.lang.ref.Cleaner and when should you use it?
?
Cleaner (Java 9+) is a mechanism that runs a Runnable cleaning action when an object becomes phantom-reachable (eligible for GC). Safer than finalizers: runs on a dedicated thread, exceptions are contained, no lock-out issue. Use only as a safety net — log a warning if close() was not explicitly called. Never rely on it as the primary resource-release mechanism (no timing guarantee). The cleaning action’s State object must not reference the owner object, or the owner can never become phantom-reachable.
What is try-with-resources and which interface must a resource implement to use it?
?
try-with-resources is a Java 7+ construct that automatically calls close() on declared resources when the try block exits. The resource must implement java.lang.AutoCloseable (or its subinterface java.io.Closeable). Resources are closed in reverse order of declaration. Multiple resources can be declared separated by semicolons.
What happens when both the try body and close() throw exceptions in try-with-resources vs. try-finally?
?
- try-with-resources: body exception propagates;
close()exception is suppressed (attached to body exception; accessible viae.getSuppressed()). The most useful exception is never lost. - try-finally:
finallyexception replaces the body exception — the body exception is completely lost with no way to retrieve it. This makes debugging very difficult.
try-with-resources is always superior.
How does Java 9 improve try-with-resources syntax?
?
Java 9 allows using effectively-final variables in try-with-resources without re-declaring them:
// Java 9+
BufferedReader reader = getReader(); // effectively final
try (reader) { // no re-declaration needed
System.out.println(reader.readLine());
}Before Java 9, you had to write try (BufferedReader r2 = reader) — redundant and confusing.
What Java 9+ collection factories replace many patterns from Item 6?
?
List.of(...), Set.of(...), Map.of(...), Map.entry(k, v) — they return unmodifiable collections backed by memory-efficient implementations. Replace:
// Old (mutable then wrap)
List<String> old = Collections.unmodifiableList(Arrays.asList("a", "b"));
// Modern
List<String> modern = List.of("a", "b"); // unmodifiable, no extra wrapper objectHow do Java 16+ records relate to Items 1 and 2?
?
Records auto-generate a canonical constructor, accessors, equals, hashCode, and toString — eliminating most boilerplate of value classes. For simple data carriers:
- They reduce the need for Item 2 Builders (though you can still add static factory methods per Item 1).
- They are ideal for immutable data transfer objects.
- They cannot extend other classes (they implicitly extend
java.lang.Record), so they do not suit hierarchies.
What is the var keyword (Java 10+) and how does it affect object creation?
?
var enables local variable type inference — the compiler infers the type from the initializer:
var list = new ArrayList<String>(); // type is ArrayList<String>
var entry = Map.entry("key", 42); // type is Map.Entry<String, Integer>It does not change object creation semantics — just reduces verbosity at declaration sites. Not allowed for fields, method parameters, or return types. Does not make Java dynamically typed.
Total Cards: 32
Review Time: ~20 minutes
Priority: HIGH
Last Updated: 2026-05-10