Chapter 5 Flashcards — Enums and Annotations

flashcards effective-java enums annotations java


What is the primary problem with the int enum pattern (static final int APPLE_FUJI = 0)?
?
The int enum pattern is not type-safe (an Orange int can be passed where an Apple int is expected), provides no namespace (requires ugly prefixes like APPLE_), produces opaque debug output (just integers), and is fragile (reordering constants breaks clients). Java enums solve all of these problems with a proper class-based type.


What makes Java enums more powerful than simple named constants?
?
Java enums are full classes. Each constant is a singleton instance of the enum class. Enums can have instance fields, constructors, methods (including abstract ones with per-constant implementations), and can implement interfaces. This enables patterns like attaching a unit conversion, a mathematical operation, or a symbol to each constant — replacing entire class hierarchies.


What is the Planet enum pattern and what does it demonstrate?
?
The Planet enum (from Effective Java) has each constant store mass and radius as instance fields, and computes surfaceGravity() and surfaceWeight(double mass) as methods. It demonstrates that enums can carry associated data (via constructor fields) and behavior (via methods), eliminating the need for parallel arrays or separate lookup tables. Usage: Planet.MARS.surfaceWeight(75).


What is constant-specific method implementation in an enum? Give an example.
?
Each enum constant provides its own implementation of an abstract method declared in the enum body — the strategy pattern without separate strategy classes:

public enum Operation {
    PLUS  { @Override public double apply(double x, double y) { return x + y; } },
    MINUS { @Override public double apply(double x, double y) { return x - y; } };
    public abstract double apply(double x, double y);
}

The compiler enforces that every constant implements the abstract method, giving you exhaustiveness safety.


Why should you never use ordinal() to derive an associated value for an enum constant?
?
ordinal() returns the constant’s position in the declaration (0-indexed). If you add, remove, or reorder constants, all derived values silently change — a maintenance time bomb. The Javadoc explicitly states ordinal() is for EnumSet/EnumMap internals only. Instead, store the meaningful value in an instance field: Ensemble(int size) { this.numberOfMusicians = size; }.


What is the correct way to do a reverse lookup (symbol → enum constant)?
?
Build a static final Map<String, Operation> at class initialization time using a stream over values():

private static final Map<String, Operation> BY_SYMBOL =
    Stream.of(values()).collect(Collectors.toMap(Object::toString, e -> e));
 
public static Optional<Operation> fromString(String symbol) {
    return Optional.ofNullable(BY_SYMBOL.get(symbol));
}

Never use ordinal() for lookups. Never iterate values() linearly — use the map.


What is the bit field pattern and what is its replacement?
?
The bit field pattern assigns each flag a power-of-2 int constant (BOLD = 1 << 0, ITALIC = 1 << 1) and combines flags with bitwise OR (BOLD | ITALIC). Its replacement is EnumSet: type-safe, readable, and internally implemented as a bit vector (same O(1) performance). Usage: EnumSet.of(Style.BOLD, Style.ITALIC). Accept Set<Style> in APIs for flexibility.


What are the key EnumSet factory methods?
?

  • EnumSet.of(E first, E... rest) — create from explicit elements
  • EnumSet.allOf(Class<E>) — all constants of the enum
  • EnumSet.noneOf(Class<E>) — empty set of the enum type
  • EnumSet.copyOf(Collection<E>) — copy any collection of enum elements
  • EnumSet.range(E from, E to) — contiguous range by declaration order
  • EnumSet.complementOf(EnumSet<E>) — all elements NOT in the given set
    All are O(1) and backed by a single long bit vector for enums with ≤ 64 constants.

Why use EnumMap instead of HashMap when keys are enum constants?
?
EnumMap is backed by an array indexed by ordinal — all operations are O(1) with negligible constants (no hashing, no collision resolution, no resizing). It is more memory-efficient than HashMap. It also maintains keys in enum declaration order and provides better toString() output. The API is identical to Map, so the change is mechanical: new EnumMap<>(MyEnum.class).


How do you build an EnumMap from a stream using groupingBy?
?
Supply an EnumMap constructor reference as the map factory to groupingBy:

Map<Plant.LifeCycle, List<Plant>> byLifeCycle =
    plants.stream()
          .collect(Collectors.groupingBy(
              p -> p.lifeCycle,
              () -> new EnumMap<>(Plant.LifeCycle.class),
              Collectors.toList()));

Without the supplier, groupingBy returns a HashMap. Providing () -> new EnumMap<>(...) ensures you get the efficient EnumMap implementation.


How do you implement a two-dimensional enum relationship (e.g., phase transitions) correctly?
?
Use a nested EnumMap built from a stream at class initialization — not a 2D array indexed by ordinals:

private static final Map<Phase, Map<Phase, Transition>> m =
    Stream.of(values()).collect(
        Collectors.groupingBy(t -> t.from,
            () -> new EnumMap<>(Phase.class),
            Collectors.toMap(t -> t.to, t -> t,
                             (x, y) -> y,
                             () -> new EnumMap<>(Phase.class))));
 
public static Transition from(Phase from, Phase to) {
    return m.get(from).get(to);
}

Adding new phases requires only a new constant and its transitions — no index arithmetic.


How do you make an enum extensible (allow clients to add their own constants)?
?
Define an interface that the enum implements. Clients write their own enums implementing the same interface:

public interface Operation { double apply(double x, double y); }
public enum BasicOp implements Operation { PLUS { public double apply(double x, double y) { return x+y; } } }
public enum ExtOp   implements Operation { EXP  { public double apply(double x, double y) { return Math.pow(x,y); } } }

Code that works with Operation works with both enums. Use <T extends Enum<T> & Operation> or Collection<? extends Operation> as parameter types.


What does Java 17’s sealed interface add to the extensible-enum pattern (Item 38)?
?
A sealed interface restricts which enums can implement it (permits BasicOp, ExtOp). This restores exhaustiveness checking in pattern-matching switch expressions (finalized Java 21): the compiler knows all implementors and requires you to handle each case. The tradeoff: sealed interfaces require all implementors to be in the same module/package, so they don’t support truly open third-party extension.


What is the naming pattern antipattern? Give an example.
?
Frameworks used to signal special treatment via method name prefixes — e.g., JUnit 3 required test methods to start with test. Problems: typos are silently ignored (a method named tsetFoo simply never runs), no parameters can be associated with the marker, no compile-time checking, and no IDE awareness. The modern replacement is annotations (@Test, @BeforeEach, etc.), which are compiler-checked and can carry structured parameters.


What are the four standard meta-annotations for defining a custom annotation?
?

  • @Retention(RetentionPolicy.RUNTIME / CLASS / SOURCE) — how long the annotation survives
  • @Target(ElementType.METHOD / TYPE / FIELD / ...) — which elements it can annotate
  • @Documented — include in Javadoc output
  • @Repeatable(ContainerAnnotation.class) — allow multiple uses on the same element

The most critical pair is @Retention + @Target. Forgetting @Retention(RUNTIME) is a common bug that causes framework reflection to silently skip annotations.


What are the three RetentionPolicy values and when is each used?
?

  • SOURCE: Discarded by the compiler. Used for source-level tools only (e.g., @SuppressWarnings, Lombok, compile-time annotation processors). Not in .class files.
  • CLASS (default): Written to .class files but not loaded by the JVM. Used by bytecode tools (ASM, bytecode weavers). Not available via reflection.
  • RUNTIME: Available via reflection at runtime. Required for any annotation that frameworks read at runtime (Spring, JPA, JUnit). Use method.isAnnotationPresent(...) and method.getAnnotation(...).

How do you define a repeatable annotation in Java 8+?
?

  1. Mark the annotation with @Repeatable(ContainerAnnotation.class).
  2. Define the container annotation with a value() method returning an array of the repeatable annotation.
@Retention(RUNTIME) @Target(METHOD) @Repeatable(ExceptionTestContainer.class)
public @interface ExceptionTest { Class<? extends Throwable> value(); }
 
@Retention(RUNTIME) @Target(METHOD)
public @interface ExceptionTestContainer { ExceptionTest[] value(); }

At the use site: @ExceptionTest(IOException.class) @ExceptionTest(RuntimeException.class).
When reading reflectively, use getAnnotationsByType(ExceptionTest.class) (not getAnnotation).


What bug does @Override prevent? Show the broken code and the fix.
?
Without @Override, you can accidentally overload instead of override. Classic example:

// BUG: overloads equals, doesn't override it — HashSet never calls this
public boolean equals(Bigram b) { return b.first == first && b.second == second; }

With @Override, this is a compile error: “Method does not override or implement a method from a supertype.” The fix:

@Override
public boolean equals(Object o) {
    if (!(o instanceof Bigram b)) return false;
    return b.first == first && b.second == second;
}

The Set now correctly deduplicates Bigram objects.


Is @Override required when implementing (not overriding) an interface method?
?
Not strictly required, but strongly recommended. If you write @Override on an interface method implementation and later change the interface (e.g., rename the method), the compiler immediately flags all stale implementations. Without @Override, the old implementation silently becomes a new method instead of an error. Effective Java recommends using @Override on all override/implementation declarations.


What is a marker interface? Give two examples from the JDK.
?
A marker interface is an interface with no methods — it merely marks a class as having some property. The interface defines a type, enabling compile-time checking.

  • java.io.Serializable: Marks classes whose instances can be serialized by ObjectOutputStream.
  • java.lang.Cloneable: Marks classes that permit Object.clone() to make field-by-field copies.
    Marker interfaces should be used when the mark applies only to class/interface declarations and compile-time type safety in method signatures is desired.

What is a marker annotation? Give two examples.
?
A marker annotation is an annotation with no elements (no parameters). It marks a program element for framework or tool consumption.

  • @Override: Marks a method as intended to override a supertype method (compile-time check).
  • @Deprecated: Marks an element as deprecated (compiler warning + Javadoc).
  • @FunctionalInterface: Marks an interface as a functional interface (compile-time check).
    Use marker annotations when the mark must apply to non-class elements (methods, fields, parameters, packages) or when you are in an annotation-heavy framework.

When should you choose a marker interface over a marker annotation?
?
Choose a marker interface when:

  1. The marker applies only to class/interface declarations (not methods or fields)
  2. You want compile-time type safety — methods that accept only marked objects can declare the interface as the parameter type (void save(Serializable obj))
  3. The marker will be used as a type bound or generic type parameter constraint

Choose a marker annotation when:

  1. The mark must apply to methods, fields, constructors, or packages
  2. You’re in a framework ecosystem that’s already annotation-heavy
  3. You anticipate adding parameters to the marker in the future (easy to do with annotations)

What does Java 14+ switch expression add to enum usage?
?
Switch expressions provide compile-time exhaustiveness checking for enum switches. If you handle every enum constant as a case label, no default branch is needed — and the compiler errors if you add a new constant and forget to handle it:

// Java 14+: no default needed — compiler verifies all cases are covered
double result = switch (op) {
    case PLUS   -> x + y;
    case MINUS  -> x - y;
    case TIMES  -> x * y;
    case DIVIDE -> x / y;
    // Add new constant? Compiler error here until you add a case.
};

This makes enum-based dispatch safer and more maintainable.


What is the EnumSet.of() performance characteristic and why?
?
EnumSet.of() and all EnumSet operations (add, remove, contains, or, and) are O(1) implemented as bitwise operations on a long (for enums with ≤ 64 constants). This is exactly as fast as manual bit-field manipulation, but with full type safety and the complete Set<E> interface. For enums with > 64 constants, EnumSet uses a long[] — still O(n/64) where n is the number of enum constants, which is effectively constant in practice.


Why does ObjectOutputStream.writeObject() accept Object instead of Serializable, and what does Item 41 say about it?
?
ObjectOutputStream.writeObject() accepts Object — a design mistake according to Bloch (Item 41). Since Serializable is a marker interface defining a type, writeObject should have accepted Serializable to catch serialization errors at compile time. Instead, errors surface at runtime with a NotSerializableException. This is the canonical example of how choosing a marker interface gives compile-time safety that a marker annotation or a runtime check cannot provide.


What is the relationship between @FunctionalInterface and marker annotations/interfaces?
?
@FunctionalInterface is a marker annotation (no parameters) that marks an interface as a functional interface (exactly one abstract method). It causes the compiler to: (1) verify the interface has exactly one abstract method (error otherwise), (2) generate the proper lambda conversion support. It is analogous to a marker interface in spirit (it marks the type’s contract) but implemented as an annotation because the check is a compiler constraint, not a type constraint used in method signatures. See ch06-lambdas-and-streams for how functional interfaces enable lambdas.


How do enums handle thread safety?
?
Enum constants are created in the static initializer of the enum class. The JVM guarantees that class initialization is thread-safe and happens exactly once per classloader. Therefore, enum constants are inherently thread-safe singletons — no double-checked locking or volatile fields needed. However, if an enum has mutable instance fields, those fields are not automatically thread-safe and must be synchronized if accessed from multiple threads.


Can enums implement interfaces? Can they extend classes?
?
Interfaces: Yes. An enum can implement any number of interfaces. This is the key mechanism for Item 38 (extensible enums via interfaces) — different enums can implement the same interface for polymorphic use.

Classes: No (with one exception). Enums implicitly extend java.lang.Enum<E> and Java allows only single inheritance. Therefore, enums cannot explicitly extend any other class. The exception: enums can have abstract methods, and each constant implicitly creates an anonymous subclass of the enum class — but this is an internal detail, not user-visible subclassing.


What is the valueOf(String name) method on enums? What happens on an invalid name?
?
MyEnum.valueOf("NAME") returns the enum constant with the given name (case-sensitive). It is generated automatically for every enum. If no constant with that name exists, it throws IllegalArgumentException. For robustness, prefer a custom reverse-lookup via a Map<String, MyEnum> built from values() (which allows case-insensitive matching, aliases, or returning Optional.empty() instead of throwing).


How do you serialize/deserialize an enum safely with JPA?
?
Use @Enumerated(EnumType.STRING) in JPA — never @Enumerated(EnumType.ORDINAL) (the default). Ordinal-based storage is fragile: reordering or inserting enum constants changes all stored ordinal values, silently corrupting data. String-based storage persists the constant’s name() — stable as long as you don’t rename constants. If you must rename a constant, use a custom @Converter that maps a fixed DB value to the enum, independent of the constant’s name.


Total Cards: 30
Review Time: ~25 minutes
Priority: HIGH
Last Updated: 2026-05-10