1. java
  2. /advanced
  3. /enums

Master Java Enums and Type-Safe Constants

Java Enums

Enums (enumerations) in Java are a special type of class that represents a group of named constants. Introduced in Java 5, enums are far more powerful than simple integer constants or string literals, providing type safety, built-in methods, and the ability to contain fields, methods, and constructors.

What are Enums?

An enum is a reference type that represents a fixed set of constants. Unlike primitive constants, enums are type-safe, self-documenting, and can contain behavior. They're particularly useful when you have a predetermined set of values that won't change, such as days of the week, compass directions, or application states.

Why Use Enums Instead of Constants?

Before enums, developers often used public static final fields:

// Old approach - error-prone and not type-safe
public class OldConstants {
    public static final int MONDAY = 1;
    public static final int TUESDAY = 2;
    public static final int WEDNESDAY = 3;
    // ... and so on
    
    // Problems with this approach:
    // 1. Not type-safe: any int can be passed
    // 2. No namespace: constants pollute the class
    // 3. No meaningful toString(): prints numbers
    // 4. No compile-time safety for new values
    // 5. Hard to iterate over all values
}

// Modern enum approach - type-safe and feature-rich
public enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

Basic Enum Syntax

The simplest enum is just a list of named constants:

public class BasicEnumExample {
    // Simple enum declaration
    public enum Priority {
        LOW, MEDIUM, HIGH, CRITICAL
    }
    
    public enum Status {
        PENDING, IN_PROGRESS, COMPLETED, CANCELLED
    }
    
    public enum Direction {
        NORTH, SOUTH, EAST, WEST
    }
    
    public static void main(String[] args) {
        // Creating enum instances
        Priority taskPriority = Priority.HIGH;
        Status projectStatus = Status.IN_PROGRESS;
        Direction heading = Direction.NORTH;
        
        // Enums have built-in toString()
        System.out.println("Task priority: " + taskPriority); // "HIGH"
        System.out.println("Project status: " + projectStatus); // "IN_PROGRESS"
        
        // Type safety - this won't compile:
        // Priority p = Status.PENDING; // Compilation error!
        
        // Comparison using == (recommended) or equals()
        if (taskPriority == Priority.HIGH) {
            System.out.println("High priority task detected!");
        }
        
        // Built-in ordinal() method returns position (starting from 0)
        System.out.println("Priority ordinal: " + taskPriority.ordinal()); // 2
        
        // Built-in name() method returns the constant name
        System.out.println("Priority name: " + taskPriority.name()); // "HIGH"
        
        // Get all enum values
        Priority[] allPriorities = Priority.values();
        System.out.println("All priorities:");
        for (Priority p : allPriorities) {
            System.out.println("  " + p + " (ordinal: " + p.ordinal() + ")");
        }
        
        // Convert string to enum
        try {
            Priority fromString = Priority.valueOf("MEDIUM");
            System.out.println("From string: " + fromString);
        } catch (IllegalArgumentException e) {
            System.out.println("Invalid enum value");
        }
    }
}

Enums with Fields and Methods

Enums can have fields, constructors, and methods, making them much more powerful than simple constants:

public class AdvancedEnumExample {
    
    // Enum with fields and methods
    public enum Planet {
        MERCURY(3.303e+23, 2.4397e6),
        VENUS(4.869e+24, 6.0518e6),
        EARTH(5.976e+24, 6.37814e6),
        MARS(6.421e+23, 3.3972e6),
        JUPITER(1.9e+27, 7.1492e7),
        SATURN(5.688e+26, 6.0268e7),
        URANUS(8.686e+25, 2.5559e7),
        NEPTUNE(1.024e+26, 2.4746e7);
        
        private final double mass;   // in kilograms
        private final double radius; // in meters
        
        // Enum constructor (always private)
        Planet(double mass, double radius) {
            this.mass = mass;
            this.radius = radius;
        }
        
        // Instance methods
        public double getMass() {
            return mass;
        }
        
        public double getRadius() {
            return radius;
        }
        
        // Calculate surface gravity
        public double surfaceGravity() {
            final double G = 6.67300E-11; // Universal gravitational constant
            return G * mass / (radius * radius);
        }
        
        // Calculate weight on this planet
        public double surfaceWeight(double otherMass) {
            return otherMass * surfaceGravity();
        }
        
        // Override toString() for custom representation
        @Override
        public String toString() {
            return name() + " (mass: " + mass + " kg, radius: " + radius + " m)";
        }
    }
    
    public static void main(String[] args) {
        double earthWeight = 70.0; // kg
        double mass = earthWeight / Planet.EARTH.surfaceGravity();
        
        System.out.println("Weight on different planets:");
        for (Planet planet : Planet.values()) {
            double weight = planet.surfaceWeight(mass);
            System.out.printf("Weight on %s: %.2f kg%n", planet.name(), weight);
        }
        
        // Access enum properties
        Planet mars = Planet.MARS;
        System.out.printf("%nMars details:%n");
        System.out.printf("Mass: %.3e kg%n", mars.getMass());
        System.out.printf("Radius: %.3e m%n", mars.getRadius());
        System.out.printf("Surface gravity: %.2f m/s²%n", mars.surfaceGravity());
    }
}

Enums with Abstract Methods

Enums can have abstract methods that each constant must implement, enabling different behavior for each enum value:

public class AbstractEnumExample {
    
    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;
            }
        },
        TIMES("*") {
            @Override
            public double apply(double x, double y) {
                return x * y;
            }
        },
        DIVIDE("/") {
            @Override
            public double apply(double x, double y) {
                if (y == 0) {
                    throw new ArithmeticException("Division by zero");
                }
                return x / y;
            }
        },
        POWER("^") {
            @Override
            public double apply(double x, double y) {
                return Math.pow(x, y);
            }
        };
        
        private final String symbol;
        
        Operation(String symbol) {
            this.symbol = symbol;
        }
        
        public String getSymbol() {
            return symbol;
        }
        
        // Abstract method that each enum constant must implement
        public abstract double apply(double x, double y);
        
        @Override
        public String toString() {
            return symbol;
        }
    }
    
    public static void main(String[] args) {
        double x = 10.0;
        double y = 3.0;
        
        System.out.println("Calculations with x = " + x + " and y = " + y + ":");
        for (Operation op : Operation.values()) {
            try {
                double result = op.apply(x, y);
                System.out.printf("%.1f %s %.1f = %.2f%n", x, op, y, result);
            } catch (ArithmeticException e) {
                System.out.printf("%.1f %s %.1f = Error: %s%n", x, op, y, e.getMessage());
            }
        }
        
        // Use specific operations
        double sum = Operation.PLUS.apply(15, 25);
        double product = Operation.TIMES.apply(6, 7);
        System.out.printf("%nDirect calculations:%n");
        System.out.printf("15 + 25 = %.0f%n", sum);
        System.out.printf("6 * 7 = %.0f%n", product);
    }
}

Real-World Examples

HTTP Status Codes

public class HttpStatusExample {
    
    public enum HttpStatus {
        // 2xx Success
        OK(200, "OK"),
        CREATED(201, "Created"),
        ACCEPTED(202, "Accepted"),
        NO_CONTENT(204, "No Content"),
        
        // 3xx Redirection
        MOVED_PERMANENTLY(301, "Moved Permanently"),
        FOUND(302, "Found"),
        NOT_MODIFIED(304, "Not Modified"),
        
        // 4xx Client Error
        BAD_REQUEST(400, "Bad Request"),
        UNAUTHORIZED(401, "Unauthorized"),
        FORBIDDEN(403, "Forbidden"),
        NOT_FOUND(404, "Not Found"),
        METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
        CONFLICT(409, "Conflict"),
        
        // 5xx Server Error
        INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
        NOT_IMPLEMENTED(501, "Not Implemented"),
        BAD_GATEWAY(502, "Bad Gateway"),
        SERVICE_UNAVAILABLE(503, "Service Unavailable");
        
        private final int code;
        private final String reasonPhrase;
        
        HttpStatus(int code, String reasonPhrase) {
            this.code = code;
            this.reasonPhrase = reasonPhrase;
        }
        
        public int getCode() {
            return code;
        }
        
        public String getReasonPhrase() {
            return reasonPhrase;
        }
        
        public boolean isSuccess() {
            return code >= 200 && code < 300;
        }
        
        public boolean isRedirection() {
            return code >= 300 && code < 400;
        }
        
        public boolean isClientError() {
            return code >= 400 && code < 500;
        }
        
        public boolean isServerError() {
            return code >= 500 && code < 600;
        }
        
        public boolean isError() {
            return isClientError() || isServerError();
        }
        
        // Find status by code
        public static HttpStatus fromCode(int code) {
            for (HttpStatus status : values()) {
                if (status.code == code) {
                    return status;
                }
            }
            throw new IllegalArgumentException("Unknown HTTP status code: " + code);
        }
        
        @Override
        public String toString() {
            return code + " " + reasonPhrase;
        }
    }
    
    public static void main(String[] args) {
        // Usage examples
        HttpStatus[] testStatuses = {
            HttpStatus.OK,
            HttpStatus.NOT_FOUND,
            HttpStatus.INTERNAL_SERVER_ERROR
        };
        
        for (HttpStatus status : testStatuses) {
            System.out.printf("Status: %s%n", status);
            System.out.printf("  Code: %d%n", status.getCode());
            System.out.printf("  Success: %b%n", status.isSuccess());
            System.out.printf("  Error: %b%n", status.isError());
            System.out.println();
        }
        
        // Find status by code
        try {
            HttpStatus status = HttpStatus.fromCode(404);
            System.out.println("Found status: " + status);
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
        
        // Group by category
        System.out.println("Success statuses:");
        for (HttpStatus status : HttpStatus.values()) {
            if (status.isSuccess()) {
                System.out.println("  " + status);
            }
        }
    }
}

State Machine with Enums

public class StateMachineExample {
    
    public enum OrderState {
        PENDING {
            @Override
            public boolean canTransitionTo(OrderState newState) {
                return newState == CONFIRMED || newState == CANCELLED;
            }
            
            @Override
            public void onEnter(Order order) {
                System.out.println("Order " + order.getId() + " is now pending");
            }
        },
        
        CONFIRMED {
            @Override
            public boolean canTransitionTo(OrderState newState) {
                return newState == PROCESSING || newState == CANCELLED;
            }
            
            @Override
            public void onEnter(Order order) {
                System.out.println("Order " + order.getId() + " confirmed");
                // Send confirmation email
            }
        },
        
        PROCESSING {
            @Override
            public boolean canTransitionTo(OrderState newState) {
                return newState == SHIPPED || newState == CANCELLED;
            }
            
            @Override
            public void onEnter(Order order) {
                System.out.println("Processing order " + order.getId());
                // Start fulfillment process
            }
        },
        
        SHIPPED {
            @Override
            public boolean canTransitionTo(OrderState newState) {
                return newState == DELIVERED;
            }
            
            @Override
            public void onEnter(Order order) {
                System.out.println("Order " + order.getId() + " shipped");
                // Generate tracking number
            }
        },
        
        DELIVERED {
            @Override
            public boolean canTransitionTo(OrderState newState) {
                return false; // Terminal state
            }
            
            @Override
            public void onEnter(Order order) {
                System.out.println("Order " + order.getId() + " delivered");
                // Send delivery confirmation
            }
        },
        
        CANCELLED {
            @Override
            public boolean canTransitionTo(OrderState newState) {
                return false; // Terminal state
            }
            
            @Override
            public void onEnter(Order order) {
                System.out.println("Order " + order.getId() + " cancelled");
                // Process refund if necessary
            }
        };
        
        // Abstract methods that each state must implement
        public abstract boolean canTransitionTo(OrderState newState);
        public abstract void onEnter(Order order);
        
        // Common method for all states
        public void transition(Order order, OrderState newState) {
            if (!canTransitionTo(newState)) {
                throw new IllegalStateException(
                    "Cannot transition from " + this + " to " + newState);
            }
            
            order.setState(newState);
            newState.onEnter(order);
        }
    }
    
    public static class Order {
        private String id;
        private OrderState state;
        
        public Order(String id) {
            this.id = id;
            this.state = OrderState.PENDING;
            state.onEnter(this);
        }
        
        public String getId() {
            return id;
        }
        
        public OrderState getState() {
            return state;
        }
        
        void setState(OrderState state) {
            this.state = state;
        }
        
        public void confirm() {
            state.transition(this, OrderState.CONFIRMED);
        }
        
        public void process() {
            state.transition(this, OrderState.PROCESSING);
        }
        
        public void ship() {
            state.transition(this, OrderState.SHIPPED);
        }
        
        public void deliver() {
            state.transition(this, OrderState.DELIVERED);
        }
        
        public void cancel() {
            state.transition(this, OrderState.CANCELLED);
        }
    }
    
    public static void main(String[] args) {
        Order order = new Order("ORD-001");
        
        try {
            // Valid state transitions
            order.confirm();
            order.process();
            order.ship();
            order.deliver();
            
            System.out.println("Final state: " + order.getState());
            
        } catch (IllegalStateException e) {
            System.out.println("Error: " + e.getMessage());
        }
        
        // Test invalid transition
        Order order2 = new Order("ORD-002");
        try {
            order2.ship(); // Invalid: can't ship from PENDING
        } catch (IllegalStateException e) {
            System.out.println("Expected error: " + e.getMessage());
        }
    }
}

Configuration and Strategy Pattern

public class ConfigurationEnumExample {
    
    public enum Environment {
        DEVELOPMENT("dev") {
            @Override
            public String getDatabaseUrl() {
                return "jdbc:h2:mem:devdb";
            }
            
            @Override
            public boolean isDebugEnabled() {
                return true;
            }
            
            @Override
            public int getMaxConnections() {
                return 5;
            }
        },
        
        TESTING("test") {
            @Override
            public String getDatabaseUrl() {
                return "jdbc:h2:mem:testdb";
            }
            
            @Override
            public boolean isDebugEnabled() {
                return true;
            }
            
            @Override
            public int getMaxConnections() {
                return 10;
            }
        },
        
        STAGING("staging") {
            @Override
            public String getDatabaseUrl() {
                return "jdbc:postgresql://staging-db:5432/myapp";
            }
            
            @Override
            public boolean isDebugEnabled() {
                return false;
            }
            
            @Override
            public int getMaxConnections() {
                return 20;
            }
        },
        
        PRODUCTION("prod") {
            @Override
            public String getDatabaseUrl() {
                return "jdbc:postgresql://prod-db:5432/myapp";
            }
            
            @Override
            public boolean isDebugEnabled() {
                return false;
            }
            
            @Override
            public int getMaxConnections() {
                return 50;
            }
        };
        
        private final String shortName;
        
        Environment(String shortName) {
            this.shortName = shortName;
        }
        
        public String getShortName() {
            return shortName;
        }
        
        // Abstract configuration methods
        public abstract String getDatabaseUrl();
        public abstract boolean isDebugEnabled();
        public abstract int getMaxConnections();
        
        // Common configuration
        public int getSessionTimeoutMinutes() {
            return this == DEVELOPMENT ? 60 : 30;
        }
        
        public String getLogLevel() {
            return isDebugEnabled() ? "DEBUG" : "INFO";
        }
        
        // Find environment by short name
        public static Environment fromShortName(String shortName) {
            for (Environment env : values()) {
                if (env.shortName.equalsIgnoreCase(shortName)) {
                    return env;
                }
            }
            throw new IllegalArgumentException("Unknown environment: " + shortName);
        }
    }
    
    public static class ApplicationConfig {
        private Environment environment;
        
        public ApplicationConfig(String envName) {
            this.environment = Environment.fromShortName(envName);
        }
        
        public void printConfiguration() {
            System.out.println("=== Application Configuration ===");
            System.out.println("Environment: " + environment.name());
            System.out.println("Database URL: " + environment.getDatabaseUrl());
            System.out.println("Debug enabled: " + environment.isDebugEnabled());
            System.out.println("Max connections: " + environment.getMaxConnections());
            System.out.println("Session timeout: " + environment.getSessionTimeoutMinutes() + " minutes");
            System.out.println("Log level: " + environment.getLogLevel());
        }
        
        public Environment getEnvironment() {
            return environment;
        }
    }
    
    public static void main(String[] args) {
        String[] environments = {"dev", "test", "staging", "prod"};
        
        for (String envName : environments) {
            try {
                ApplicationConfig config = new ApplicationConfig(envName);
                config.printConfiguration();
                System.out.println();
            } catch (IllegalArgumentException e) {
                System.out.println("Error: " + e.getMessage());
            }
        }
    }
}

Enum Best Practices

1. Use Enums for Type Safety

public class EnumBestPractices {
    
    // BAD: Using strings for status
    public class BadTaskOld {
        private String status; // Can be any string!
        
        public void setStatus(String status) {
            this.status = status; // No validation
        }
        
        public boolean isCompleted() {
            return "completed".equals(status); // Error-prone string comparison
        }
    }
    
    // GOOD: Using enum for status
    public enum TaskStatus {
        TODO, IN_PROGRESS, COMPLETED, CANCELLED
    }
    
    public class GoodTask {
        private TaskStatus status;
        
        public void setStatus(TaskStatus status) {
            this.status = status; // Type-safe
        }
        
        public boolean isCompleted() {
            return status == TaskStatus.COMPLETED; // Safe comparison
        }
    }
    
    // 2. Prefer enums over boolean flags when there are more than 2 states
    
    // BAD: Boolean doesn't scale
    public class BadNotificationOld {
        private boolean sent; // What about failed? Pending? Retrying?
    }
    
    // GOOD: Enum is extensible
    public enum NotificationStatus {
        PENDING, SENT, FAILED, RETRYING
    }
    
    public class GoodNotification {
        private NotificationStatus status;
    }
    
    // 3. Use enums with switch statements
    public void processTaskStatus(TaskStatus status) {
        switch (status) {
            case TODO:
                System.out.println("Task needs to be started");
                break;
            case IN_PROGRESS:
                System.out.println("Task is being worked on");
                break;
            case COMPLETED:
                System.out.println("Task is finished");
                break;
            case CANCELLED:
                System.out.println("Task was cancelled");
                break;
            // No default needed - compiler ensures all cases are covered
        }
    }
    
    // 4. Use EnumSet for collections of enums
    public void demonstrateEnumSet() {
        EnumSet<TaskStatus> activeStatuses = EnumSet.of(
            TaskStatus.TODO, 
            TaskStatus.IN_PROGRESS
        );
        
        EnumSet<TaskStatus> terminalStatuses = EnumSet.of(
            TaskStatus.COMPLETED, 
            TaskStatus.CANCELLED
        );
        
        TaskStatus currentStatus = TaskStatus.IN_PROGRESS;
        
        if (activeStatuses.contains(currentStatus)) {
            System.out.println("Task is active");
        }
        
        if (terminalStatuses.contains(currentStatus)) {
            System.out.println("Task is finished");
        }
    }
    
    // 5. Use EnumMap for enum-keyed maps
    public void demonstrateEnumMap() {
        EnumMap<TaskStatus, String> statusDescriptions = new EnumMap<>(TaskStatus.class);
        statusDescriptions.put(TaskStatus.TODO, "Not started yet");
        statusDescriptions.put(TaskStatus.IN_PROGRESS, "Currently being worked on");
        statusDescriptions.put(TaskStatus.COMPLETED, "Finished successfully");
        statusDescriptions.put(TaskStatus.CANCELLED, "Was cancelled");
        
        for (TaskStatus status : TaskStatus.values()) {
            System.out.println(status + ": " + statusDescriptions.get(status));
        }
    }
}

2. Implementing Interfaces

public class EnumInterfaceExample {
    
    // Enums can implement interfaces
    public interface Executable {
        void execute();
        String getDescription();
    }
    
    public enum Command implements Executable {
        SAVE {
            @Override
            public void execute() {
                System.out.println("Saving data...");
            }
            
            @Override
            public String getDescription() {
                return "Save current data to storage";
            }
        },
        
        LOAD {
            @Override
            public void execute() {
                System.out.println("Loading data...");
            }
            
            @Override
            public String getDescription() {
                return "Load data from storage";
            }
        },
        
        DELETE {
            @Override
            public void execute() {
                System.out.println("Deleting data...");
            }
            
            @Override
            public String getDescription() {
                return "Delete data from storage";
            }
        };
        
        // Common method for all commands
        public void executeWithLogging() {
            System.out.println("Executing: " + getDescription());
            execute();
            System.out.println("Command completed");
        }
    }
    
    public static void main(String[] args) {
        Command[] commands = {Command.SAVE, Command.LOAD, Command.DELETE};
        
        for (Command cmd : commands) {
            cmd.executeWithLogging();
            System.out.println();
        }
        
        // Use as interface
        Executable executable = Command.SAVE;
        executable.execute();
    }
}

Performance Considerations

Enums are efficient and performant:

public class EnumPerformance {
    
    public enum Color {
        RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, BLACK, WHITE
    }
    
    public static void main(String[] args) {
        // Enum comparison is very fast (uses ==)
        Color color1 = Color.RED;
        Color color2 = Color.RED;
        
        // This is fast - reference equality
        boolean same = (color1 == color2);
        
        // This also works but == is preferred for enums
        boolean sameEquals = color1.equals(color2);
        
        // EnumSet is highly optimized for enums
        EnumSet<Color> primaryColors = EnumSet.of(Color.RED, Color.GREEN, Color.BLUE);
        EnumSet<Color> warmColors = EnumSet.of(Color.RED, Color.YELLOW, Color.ORANGE);
        
        // Set operations are very efficient with EnumSet
        EnumSet<Color> intersection = EnumSet.copyOf(primaryColors);
        intersection.retainAll(warmColors);
        
        System.out.println("Primary colors: " + primaryColors);
        System.out.println("Warm colors: " + warmColors);
        System.out.println("Intersection: " + intersection);
        
        // EnumMap is also highly optimized
        EnumMap<Color, String> colorNames = new EnumMap<>(Color.class);
        colorNames.put(Color.RED, "Rouge");
        colorNames.put(Color.GREEN, "Vert");
        colorNames.put(Color.BLUE, "Bleu");
        
        // Very fast lookups
        String frenchRed = colorNames.get(Color.RED);
        System.out.println("Red in French: " + frenchRed);
    }
}

Common Pitfalls

1. Ordinal Dependency

public class EnumPitfalls {
    
    // BAD: Don't depend on ordinal() for business logic
    public enum BadPriority {
        LOW, MEDIUM, HIGH, CRITICAL;
        
        // BAD: This breaks if enum order changes
        public boolean isHigherThan(BadPriority other) {
            return this.ordinal() > other.ordinal();
        }
    }
    
    // GOOD: Use explicit values or methods
    public enum GoodPriority {
        LOW(1), MEDIUM(2), HIGH(3), CRITICAL(4);
        
        private final int level;
        
        GoodPriority(int level) {
            this.level = level;
        }
        
        public boolean isHigherThan(GoodPriority other) {
            return this.level > other.level;
        }
        
        public int getLevel() {
            return level;
        }
    }
    
    // 2. Serialization issues
    public static void demonstrateSerializationIssue() {
        // If you add/remove enum values, serialization can break
        // Always use explicit serialVersionUID and be careful with changes
    }
    
    public static void main(String[] args) {
        GoodPriority high = GoodPriority.HIGH;
        GoodPriority medium = GoodPriority.MEDIUM;
        
        System.out.println(high + " is higher than " + medium + ": " + 
                          high.isHigherThan(medium));
        
        // Show why ordinal is unreliable
        System.out.println("HIGH ordinal: " + high.ordinal());
        System.out.println("HIGH level: " + high.getLevel());
    }
}

Summary

Java enums are powerful constructs that provide:

Key Benefits:

  • Type Safety: Compile-time checking prevents invalid values
  • Self-Documenting: Clear, readable code with meaningful names
  • Rich Functionality: Can contain fields, methods, and constructors
  • Performance: Optimized implementations with fast comparisons
  • Extensibility: Easy to add behavior and maintain

Core Features:

  • Constants: Predefined set of named values
  • Methods: Built-in values(), valueOf(), ordinal(), name()
  • Fields and Constructors: Custom data and initialization
  • Abstract Methods: Different behavior per constant
  • Interface Implementation: Can implement interfaces

Best Practices:

  • Use enums instead of string/int constants for type safety
  • Prefer enum fields over ordinal() for business logic
  • Use == for enum comparison (faster than equals())
  • Leverage EnumSet and EnumMap for collections
  • Implement interfaces when enums need common behavior
  • Use abstract methods for constant-specific behavior

When to Use:

  • Fixed set of constants (days, states, types, etc.)
  • Configuration values with behavior
  • State machines and strategy patterns
  • Type-safe alternatives to boolean flags
  • API parameters with limited valid values

Enums represent one of Java's most elegant features, providing type safety, expressiveness, and powerful capabilities while maintaining simplicity. They should be your go-to choice whenever you need a fixed set of constants with associated behavior.