15 questions found
What is an enum in Java, and how does it differ from simply using a set of integer or String constants?
Beginner
An enum defines a fixed, type-safe set of named constants, where each constant is actually a full singleton instance of the enum type itself -- unlike int or String constants (which provide no compile-time type safety, allowing any arbitrary int/String value to be passed where a specific constant was intended), an enum's type system prevents passing an invalid value at all, since only the declared enum constants can exist as values of that type, catching a whole class of "invalid magic value" bugs at compile time rather than at runtime.
public enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
public void scheduleTask(Day day) { ... }
scheduleTask(Day.MONDAY); // type-safe, only a valid Day constant can be passed
// scheduleTask(1); // wouldn't even compile -- contrast with an int-constant-based approach
// where scheduleTask(99) would compile fine despite being a meaningless, invalid value
Real-world example
A legacy codebase using int constants (STATUS_PENDING = 0, STATUS_APPROVED = 1) for an order's status is refactored to use an OrderStatus enum, immediately eliminating an entire class of bugs where an invalid or out-of-range integer could previously be silently passed and stored without any compile-time or even runtime detection.
Common follow-ups: Why can't you create a new instance of an enum type using 'new' the way you can with a regular class?;How does the compiler guarantee that only the declared enum constants can ever exist as values of that type?
OOP & Classes;Java Fundamentals: Syntax
Data Types & Operators
How do you add fields, constructors, and methods to a Java enum, and how would you implement an enum representing planets with mass and radius that calculates surface gravity?
Intermediate
Since each enum constant is a singleton instance, an enum can have instance fields (typically final, set via a private constructor invoked implicitly for each constant), and both regular instance methods (shared logic across all constants) and constant-specific method bodies (where an individual constant overrides a method uniquely for itself) -- letting an enum encapsulate not just a name but genuine associated data and behavior, going well beyond what a simple set of constants could express.
public enum Planet {
MERCURY(3.303e+23, 2.4397e6),
EARTH(5.976e+24, 6.37814e6);
private final double mass; // kg
private final double radius; // meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
public double surfaceGravity() {
final double G = 6.67300E-11;
return G * mass / (radius * radius);
}
}
System.out.println(Planet.EARTH.surfaceGravity());
Real-world example
A physics simulation models each solar system planet as an enum constant carrying its own mass and radius, with a shared surfaceGravity() calculation method available uniformly across every planet constant, encapsulating both the astronomical data and the derived calculation logic in one type-safe, self-documenting structure.
Common follow-ups: Why must an enum's constructor always be implicitly private, and what happens if you try to make it public?;How would you implement a method with a DIFFERENT implementation for each individual enum constant (constant-specific method bodies)?
OOP & Classes;Design Patterns in Java
How would you implement the Strategy pattern using an enum with constant-specific method bodies, and how does this compare to a traditional interface-based Strategy implementation?
Advanced
An enum can have each individual constant override an abstract method with its own unique implementation (a "constant-specific class body"), letting the enum itself directly encapsulate a fixed, closed set of interchangeable algorithms/strategies without needing separate implementation classes -- this is a particularly clean fit specifically when the set of strategies is genuinely fixed and known in advance (unlike a general Strategy pattern via an interface, which supports an open-ended, extensible set of implementations), trading that extensibility for enum's built-in singleton-per-constant simplicity and exhaustiveness-checkable switch support.
public enum Operation {
ADD { public double apply(double a, double b) { return a + b; } },
SUBTRACT { public double apply(double a, double b) { return a - b; } },
MULTIPLY { public double apply(double a, double b) { return a * b; } };
public abstract double apply(double a, double b); // each constant provides its own implementation
}
double result = Operation.ADD.apply(3, 4); // 7.0, dispatches directly to ADD's own implementation
Real-world example
A simple calculator's arithmetic operations are modeled as an Operation enum with constant-specific apply() implementations, avoiding both a large if/else or switch chain AND the need for four separate small implementation classes that a traditional interface-based Strategy pattern would require for this same fixed, closed set of operations.
Common follow-ups: When would a traditional interface-based Strategy implementation still be preferable to this enum-based approach?;What's the memory/performance characteristic of constant-specific method bodies compared to a switch-based dispatch?
Design Patterns in Java;Functional Interfaces & Method References
How does the EnumSet and EnumMap provide more efficient alternatives to HashSet<Enum> and HashMap<Enum, V> specifically for enum keys?
Intermediate
EnumSet and EnumMap are specialized collection implementations designed exclusively for use with enum types, internally represented as a compact bitvector (EnumSet) or array indexed directly by each constant's ordinal() (EnumMap), giving them dramatically better memory efficiency and performance than a generic HashSet/HashMap would achieve for the same enum-keyed data (avoiding hashing overhead entirely, since the ordinal provides a direct, dense array index) -- the JDK documentation specifically recommends these specialized types whenever working with sets or maps keyed by enum constants, since there's essentially no downside to using them over the generic alternatives in this specific scenario.
enum Permission { READ, WRITE, EXECUTE, DELETE }
// EnumSet: highly efficient bitvector-backed set, ideal for enum flag combinations
EnumSet<Permission> userPermissions = EnumSet.of(Permission.READ, Permission.WRITE);
// EnumMap: array-backed map, far more efficient than HashMap<Permission, String> for this use case
EnumMap<Permission, String> descriptions = new EnumMap<>(Permission.class);
descriptions.put(Permission.READ, "Can view content");
Real-world example
A file permission system representing a set of granted permissions (READ, WRITE, EXECUTE) per user uses EnumSet<Permission> rather than a generic HashSet<Permission>, gaining both meaningfully better memory efficiency and faster set operations (union, intersection) purpose-built for exactly this enum-based use case.
Common follow-ups: How does EnumSet's internal bitvector representation make operations like union/intersection especially fast?;What happens if you try to use EnumMap or EnumSet with a null key -- is it permitted?
Collections Framework;Diagnostics & Performance
How does Java implement enums under the hood at the bytecode level (extending java.lang.Enum), and what makes them inherently thread-safe and serialization-safe as singletons?
Advanced
Every enum implicitly extends java.lang.Enum<T> (making explicit inheritance of another class impossible, since Java lacks multiple inheritance), with each constant compiled as a public static final field initialized exactly once during the enum class's static initialization (a process the JVM itself guarantees is thread-safe and happens exactly once, even under concurrent class loading) -- this JVM-guaranteed single-initialization behavior is precisely what makes enum-based Singletons inherently thread-safe without any explicit synchronization needed; additionally, the JVM's default serialization mechanism for enums deliberately serializes only the constant's name (not its fields), deserializing by looking up the matching constant via valueOf() rather than creating a new instance, which is what makes enum singletons immune to the classic serialization-based Singleton-breaking attack that affects a conventional private-constructor Singleton implementation.
// Decompiled bytecode conceptually resembles:
public abstract class Day extends Enum<Day> {
public static final Day MONDAY = new Day("MONDAY", 0);
// ... other constants
private static final Day[] VALUES = { MONDAY, /* ... */ };
public static Day[] values() { return VALUES.clone(); }
public static Day valueOf(String name) { /* looks up by name */ }
}
Real-world example
A security-conscious library specifically chooses an enum-based Singleton over the classic private-constructor approach precisely because enum's special JVM-guaranteed serialization behavior (deserializing to the existing constant rather than creating a new instance) closes off a known technique for breaking Singleton uniqueness via crafted serialized data, a vulnerability the classic approach requires extra defensive code to prevent.
Common follow-ups: Why can't an enum extend any other class, given it already implicitly extends java.lang.Enum?;What specific serialization attack does the classic private-constructor Singleton remain vulnerable to that enum-based Singleton avoids?
Concurrency & Threads;Design Patterns in Java
How would you implement an enum that also implements an interface, and what's the benefit of having different enum constants provide different behavior through a shared interface contract?
Intermediate
An enum can implement one or more interfaces just like a regular class, either providing a single shared implementation for all constants, or combined with constant-specific method bodies to give each constant its own distinct implementation of the interface's methods -- this is useful when you want the type-safety and exhaustiveness benefits of an enum while still being able to pass enum constants around polymorphically wherever the interface type is expected, integrating enums cleanly into broader interface-based designs.
public interface Discount {
double apply(double price);
}
public enum DiscountType implements Discount {
NONE { public double apply(double price) { return price; } },
PERCENT_10 { public double apply(double price) { return price * 0.9; } },
FLAT_5 { public double apply(double price) { return Math.max(0, price - 5); } };
}
Discount discount = DiscountType.PERCENT_10; // usable polymorphically anywhere a Discount is expected
System.out.println(discount.apply(100.0));
Real-world example
A pricing engine defines DiscountType as an enum implementing a shared Discount interface, letting checkout code accept any Discount polymorphically (whether it's a fixed enum constant or, in theory, some other non-enum Discount implementation), while still getting the enum's built-in enumeration and switch-exhaustiveness benefits for the fixed set of standard discount types.
Common follow-ups: Can an enum implement multiple interfaces simultaneously, given it can't extend multiple classes?;How does this pattern compare to the constant-specific-method-body Strategy pattern example without an explicit interface?
Design Patterns in Java;Interfaces & Abstract Classes
How would you use an EnumMap combined with a switch expression to implement a state machine, and what compile-time safety benefits does an exhaustive switch over an enum provide?
Advanced
Modeling a state machine's states as an enum, with transitions defined either via an EnumMap<State, Map<Event, State>> lookup table or a switch expression over the current state and incoming event, gives you a structure the compiler can help verify is exhaustive (a switch expression over an enum, especially without a default branch in modern Java, forces you to explicitly handle every enum constant, immediately flagging at compile time if a new state is added but its transition handling was forgotten) -- this compile-time exhaustiveness checking is a meaningful safety benefit over an equivalent if/else chain or a Map-based lookup that would fail only at runtime (or silently do nothing) if a state was missed.
enum State { IDLE, RUNNING, PAUSED, STOPPED }
enum Event { START, PAUSE, RESUME, STOP }
State transition(State current, Event event) {
return switch (current) {
case IDLE -> event == Event.START ? State.RUNNING : current;
case RUNNING -> switch (event) {
case PAUSE -> State.PAUSED;
case STOP -> State.STOPPED;
default -> current;
};
case PAUSED -> event == Event.RESUME ? State.RUNNING : current;
case STOPPED -> current;
// Compiler ERROR if a new State constant is added but not handled here (no default case)
};
}
Real-world example
An order-processing state machine (PENDING, PAID, SHIPPED, DELIVERED, CANCELLED) implemented with an exhaustive switch expression immediately produces a compile error when a new CANCELLED_REFUNDED state is added to the enum but its transition logic is forgotten in the switch, catching an incomplete state machine implementation at build time rather than allowing a runtime bug to reach production.
Common follow-ups: What happens if you DO include a default case in an otherwise-exhaustive enum switch -- does it lose the exhaustiveness-checking benefit?;How would you implement this same state machine using an EnumMap-based transition table instead of a switch expression, and what are the trade-offs?
Pattern Matching & Switch Expressions;Design Patterns in Java
What are the ordinal() and name() methods on an enum constant, and why is relying on ordinal() for persistence or serialization considered dangerous?
Intermediate
ordinal() returns the zero-based position of a constant in its enum's declaration order, and name() returns the constant's declared identifier as a String -- persisting an enum's ordinal() value (e.g., storing it in a database) is dangerous because ordinal values are entirely dependent on declaration order, meaning simply reordering, inserting, or removing a constant in the enum's source code (a seemingly harmless refactor) silently changes the meaning of every previously-persisted ordinal value, causing data corruption without any compiler warning; persisting name() instead is far safer since it's tied to the constant's actual identifier rather than its fragile positional order (though renaming a constant would still require a data migration, that's a much rarer and more deliberate change than reordering).
enum Priority { LOW, MEDIUM, HIGH } // ordinals: LOW=0, MEDIUM=1, HIGH=2
// DANGEROUS: persisting ordinal()
int stored = Priority.HIGH.ordinal(); // stores 2
// If someone later reorders the enum to: LOW, HIGH, MEDIUM
// then ordinal 2 now means MEDIUM instead of HIGH -- silent data corruption!
// SAFER: persist name() instead
String stored2 = Priority.HIGH.name(); // stores "HIGH", immune to reordering
Real-world example
A team debugging why historical order priority data appeared to have silently changed meaning after a routine code refactor discovers the database had been storing Priority.ordinal() values, and a well-intentioned enum reordering during an unrelated change had silently invalidated the meaning of every previously stored value, prompting a migration to storing name() instead.
Common follow-ups: What's the safest approach if you need a genuinely stable, explicit persisted value that's independent of both ordinal and name (like an explicit numeric code field)?;Why does Comparable's compareTo() implementation for enums use ordinal() despite this same fragility concern?
equals()
hashCode() & toString() Contracts;Serialization & Deserialization
How would you implement a custom enum that also functions as a lookup table (mapping an external code to a constant), such as parsing an HTTP status code string into an enum constant, handling unknown values gracefully?
Advanced
Rather than relying on the built-in valueOf() (which throws IllegalArgumentException for any unrecognized name and requires an exact case-sensitive match to the constant's declared identifier), a more robust approach adds a custom static lookup method backed by a private static Map<String, EnumType> built once during static initialization, mapping external representations (which might differ from the enum's own Java identifier naming conventions) to the corresponding constant, with an explicit fallback (like returning an UNKNOWN sentinel constant, or throwing a custom, more descriptive exception) for values that don't match any known constant.
public enum HttpStatus {
OK(200), NOT_FOUND(404), SERVER_ERROR(500), UNKNOWN(-1);
private final int code;
private static final Map<Integer, HttpStatus> BY_CODE = new HashMap<>();
static {
for (HttpStatus status : values()) { BY_CODE.put(status.code, status); }
}
HttpStatus(int code) { this.code = code; }
public static HttpStatus fromCode(int code) {
return BY_CODE.getOrDefault(code, UNKNOWN); // graceful fallback instead of throwing
}
}
Real-world example
An HTTP client library parsing status codes from external API responses uses a custom fromCode() lookup method (built on a precomputed static Map) rather than relying on the enum's own name-based valueOf(), gracefully handling any unexpected or non-standard status code by falling back to an UNKNOWN constant instead of throwing an exception that would crash response parsing entirely.
Common follow-ups: Why is building the lookup Map in a static initializer block preferable to constructing it lazily on first use?;What's the trade-off of returning a sentinel UNKNOWN constant versus throwing a custom exception for unrecognized input?
Exceptions;Java Fundamentals: Syntax
Data Types & Operators
How do you correctly use an enum as a switch statement/expression's target, and what changed with the introduction of arrow-style switch expressions (Java 14+) regarding exhaustiveness and fall-through?
Intermediate
Traditional colon-style switch statements over an enum use unqualified constant names in each case label (not Day.MONDAY, just MONDAY) and are prone to accidental fall-through if a break is forgotten; the newer arrow-style switch expressions (case X -> ...) eliminate fall-through entirely (each branch is independent, no accidental cascade into the next case), can directly return a value as an expression, and when covering every enum constant without a default branch, give the compiler the ability to verify exhaustiveness at compile time, immediately flagging if a new constant is later added without a corresponding case being handled.
enum Size { SMALL, MEDIUM, LARGE }
// Old style: fall-through risk if break is forgotten
switch (size) {
case SMALL: System.out.println("S"); break;
case MEDIUM: System.out.println("M"); break;
case LARGE: System.out.println("L"); break;
}
// Modern arrow style: no fall-through risk, can be an expression, exhaustiveness-checkable
String label = switch (size) {
case SMALL -> "S";
case MEDIUM -> "M";
case LARGE -> "L";
};
Real-world example
A refactor from an old colon-style switch statement (which had a latent bug where a missing break caused unintended fall-through between two size cases) to the modern arrow-style switch expression both fixes the fall-through bug structurally (impossible in the new syntax) and gains compile-time exhaustiveness checking as a bonus safety improvement.
Common follow-ups: What happens if you DO include a default case in an arrow-style switch over an enum -- does the compiler still warn about missing constants?;Why was colon-style switch's fall-through behavior originally designed that way, and is it ever still useful?
Pattern Matching & Switch Expressions;Java Fundamentals: Syntax
Data Types & Operators