Java · lesson 59 of 66
Beginner
17- 1 Introduction to Java
- 2 Setting Up Java
- 3 Data Types in Java
- 4 Operators in Java
- 5 Variables and Operators in Java
- 6 Control Flow in Java
- 7 Arrays in Java
- 8 Functions (Methods) in Java
- 9 Strings in Java
- 10 Methods in Java
- 11 Java OOP — Complete Guide
- 12 Introduction to Object-Oriented Programming in Java
- 13 Encapsulation in Java
- 14 Inheritance in Java
- 15 Polymorphism in Java
- 17 Abstraction in Java
- 18 Error Types in Java
Intermediate
38- 16 Regular Expressions in Java
- 19 Interfaces in Java — A Deep Dive
- 20 Abstract Classes in Java
- 21 Method Overloading in Java
- 22 Composition vs Inheritance in Java
- 23 Generics in Java
- 27 Exception Handling in Java
- 28 Java Streams API
- 29 Collections Framework in Java
- 30 Optional in Java
- 31 File Handling in Java
- 32 Records in Java
- 33 Java 8 Features
- 34 Sealed Classes and Interfaces in Java
- 35 Multithreading in Java
- 37 JDBC — Database Access in Java
- 39 Maven — Java Build and Dependency Management
- 42 Gradle — Modern Java Build Tool
- 44 Unit Testing with JUnit 5 and Mockito
- 45 Logging in Java with SLF4J and Logback
- 49 Spring Boot — Introduction
- 50 Dependency Injection in Spring Boot
- 51 Java Project Ideas
- 52 Building REST APIs with Spring Boot
- 53 Validation in Spring Boot
- 54 Spring Data JPA — Database Access
- 55 Exception Handling in Spring Boot
- 56 Spring Security — Authentication and Authorization
- 57 JWT Authentication in Spring Boot
- 58 Dockerizing Spring Boot Applications
- 59 Java Enums: Beyond Simple Constants
- 60 Inner and Anonymous Classes in Java
- 61 Java Generics: Bounded Types, Wildcards, and Generic Methods
- 62 Java Date/Time API: LocalDate, LocalDateTime, and Duration
- 63 Java NIO.2: Paths, Files, and WatchService
- 64 Java Annotations: Custom Types and Meta-Information
- 65 Java Reflection: Dynamic Type Inspection and Method Invocation
- 66 Java Localization: ResourceBundle and Locale Support
Advanced
11Java Enums: Beyond Simple Constants
Master Java enums with fields, constructors, abstract methods, and methods — when to use them over constants and how they enable type-safe state machines.
By SkillByExample · Updated
An enum is a class with a fixed set of instances, one per constant. Because it is a real class,
each constant can carry fields, the enum can have methods, and constants can even override
methods individually. Every example is a single file run with the JDK 21 source launcher
(java File.java).
Enums are type-safe
Compare an enum with the int constants it replaces.
public class TypeSafety {
static final int MONDAY = 1, SATURDAY = 6;
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
static void scheduleInt(int day) { System.out.println("int day " + day); }
static void schedule(Day day) { System.out.println("enum day " + day); }
public static void main(String[] args) {
scheduleInt(SATURDAY);
scheduleInt(42); // compiles: any int is accepted
schedule(Day.SATURDAY);
schedule(42);
}
}
$ java TypeSafety.java
TypeSafety.java:12: error: method schedule in class TypeSafety cannot be applied to given types;
schedule(42);
^
required: Day
found: int
reason: argument mismatch; int cannot be converted to Day
1 error
error: compilation failed
scheduleInt(42) is accepted even though 42 is not a day. With the enum, only the seven Day
constants (or null) can ever be passed. Remove the last line and the program prints
int day 6, int day 42, enum day SATURDAY.
Constants with fields
Each constant passes arguments to a constructor, which is implicitly private and runs once per
constant when the enum is loaded.
public class Planets {
enum Planet {
MERCURY(3.303e+23, 2.4397e6),
EARTH(5.976e+24, 6.37814e6),
MARS(6.421e+23, 3.3972e6);
private final double mass, radius;
Planet(double mass, double radius) { this.mass = mass; this.radius = radius; }
double surfaceGravity() { return 6.67300E-11 * mass / (radius * radius); }
}
public static void main(String[] args) {
for (Planet p : Planet.values()) {
System.out.printf("%-8s %5.2f m/s^2%n", p, p.surfaceGravity());
}
double earthWeight = 70;
double mass = earthWeight / Planet.EARTH.surfaceGravity();
System.out.printf("70 kg-weight on Mars: %.1f%n", mass * Planet.MARS.surfaceGravity());
}
}
$ java Planets.java
MERCURY 3.70 m/s^2
EARTH 9.80 m/s^2
MARS 3.71 m/s^2
70 kg-weight on Mars: 26.5
The data lives with the constant, so there is no parallel lookup table to keep in sync.
values() returns the constants in declaration order.
Built-in methods: values, valueOf, name, ordinal
public class BuiltIns {
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
public static void main(String[] args) {
System.out.println(Day.values().length + " days, first " + Day.values()[0]);
Day d = Day.valueOf("FRIDAY");
System.out.println(d.name() + " ordinal=" + d.ordinal() + " compareTo(MONDAY)=" + d.compareTo(Day.MONDAY));
System.out.println(Day.valueOf("friday".toUpperCase()));
System.out.println(Day.valueOf("Friday"));
}
}
$ java BuiltIns.java
7 days, first MONDAY
FRIDAY ordinal=4 compareTo(MONDAY)=4
FRIDAY
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant BuiltIns.Day.Friday
at java.base/java.lang.Enum.valueOf(Enum.java:293)
at BuiltIns$Day.valueOf(BuiltIns.java:2)
at BuiltIns.main(BuiltIns.java:9)
valueOf is exact and case-sensitive, so normalise input before calling it. ordinal() is the
zero-based position, and enums compare by it. Don’t store ordinals in a database: inserting a
constant in the middle renumbers everything after it.
Methods and switch
Constants are singletons, so == compares them safely, and a switch expression over an enum
needs no default when it lists every constant.
public class Weekend {
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
boolean isWeekend() { return this == SATURDAY || this == SUNDAY; }
}
static int hours(Day d) {
return switch (d) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY -> 8;
case FRIDAY -> 6;
case SATURDAY, SUNDAY -> 0;
};
}
public static void main(String[] args) {
for (Day d : Day.values()) System.out.print(d + "=" + hours(d) + (d.isWeekend() ? "* " : " "));
System.out.println();
}
}
$ java Weekend.java
MONDAY=8 TUESDAY=8 WEDNESDAY=8 THURSDAY=8 FRIDAY=6 SATURDAY=0* SUNDAY=0*
The switch is exhaustive: delete the SATURDAY, SUNDAY line and compilation fails with “the
switch expression does not cover all possible input values”. That error is the reason to prefer
listing constants over a default — adding an eighth constant later flags every switch that
forgot it.
Constant-specific behaviour
An enum can declare an abstract method that every constant implements in its own body.
public class Calculator {
enum Operation {
PLUS("+") { double apply(double x, double y) { return x + y; } },
MINUS("-") { double apply(double x, double y) { return x - y; } },
TIMES("*") { double apply(double x, double y) { return x * y; } },
DIVIDE("/") { double apply(double x, double y) { return x / y; } };
final String symbol;
Operation(String symbol) { this.symbol = symbol; }
abstract double apply(double x, double y);
}
public static void main(String[] args) {
for (Operation op : Operation.values()) {
System.out.println("10 " + op.symbol + " 4 = " + op.apply(10, 4));
}
}
}
$ java Calculator.java
10 + 4 = 14.0
10 - 4 = 6.0
10 * 4 = 40.0
10 / 4 = 2.5
Each constant is an anonymous subclass of Operation. Forgetting apply on a new constant is a
compile error, unlike a switch elsewhere that someone forgets to update.
EnumMap and EnumSet
The collections specialised for enum keys are compact arrays and bit sets internally, and they iterate in declaration order.
import java.util.*;
public class EnumCollections {
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
public static void main(String[] args) {
EnumSet<Day> weekend = EnumSet.of(Day.SATURDAY, Day.SUNDAY);
EnumSet<Day> weekdays = EnumSet.complementOf(weekend);
System.out.println(weekdays);
System.out.println(EnumSet.range(Day.TUESDAY, Day.THURSDAY));
Map<Day, Integer> meetings = new EnumMap<>(Day.class);
meetings.put(Day.FRIDAY, 1);
meetings.put(Day.MONDAY, 3);
System.out.println(meetings);
}
}
$ java EnumCollections.java
[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY]
[TUESDAY, WEDNESDAY, THURSDAY]
{MONDAY=3, FRIDAY=1}
FRIDAY was put first, but the EnumMap prints MONDAY first: it orders by declaration, not
insertion. Use these instead of HashSet/HashMap whenever the keys are enum constants.
Practice
- Write a
LogLevelenum —ERROR(4),WARN(3),INFO(2),DEBUG(1)— withshouldLog(LogLevel threshold). Print which levels are logged when the threshold isWARN.
Solution
public class Levels {
enum LogLevel {
ERROR(4), WARN(3), INFO(2), DEBUG(1);
private final int severity;
LogLevel(int severity) { this.severity = severity; }
boolean shouldLog(LogLevel threshold) { return severity >= threshold.severity; }
}
public static void main(String[] args) {
for (LogLevel l : LogLevel.values()) {
System.out.println(l + " logged? " + l.shouldLog(LogLevel.WARN));
}
}
}
$ java Levels.java
ERROR logged? true
WARN logged? true
INFO logged? false
DEBUG logged? false
Taking a LogLevel rather than a raw int keeps the threshold type-safe too.
- Model a traffic light as an enum where each constant implements
next(), and print six transitions starting fromRED.
Solution
public class Traffic {
enum Light {
RED { Light next() { return GREEN; } },
GREEN { Light next() { return YELLOW; } },
YELLOW { Light next() { return RED; } };
abstract Light next();
}
public static void main(String[] args) {
Light l = Light.RED;
StringBuilder sb = new StringBuilder(l.toString());
for (int i = 0; i < 6; i++) { l = l.next(); sb.append(" -> ").append(l); }
System.out.println(sb);
}
}
$ java Traffic.java
RED -> GREEN -> YELLOW -> RED -> GREEN -> YELLOW -> RED
A state machine as an enum: every state must define its transition, and no invalid state can exist.
- Write
parseDay(String)that accepts any case and returns anOptional<Day>, empty for unknown names instead of throwing.
Solution
import java.util.Optional;
public class ParseDay {
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
static Optional<Day> parseDay(String s) {
try {
return Optional.of(Day.valueOf(s.strip().toUpperCase()));
} catch (IllegalArgumentException e) {
return Optional.empty();
}
}
public static void main(String[] args) {
System.out.println(parseDay(" friday "));
System.out.println(parseDay("Funday"));
}
}
$ java ParseDay.java
Optional[FRIDAY]
Optional.empty
Catching the exception at the boundary keeps the rest of the code free of string checks.
- Count words by their
Dayin"MONDAY FRIDAY MONDAY SUNDAY"using anEnumMap.
Solution
import java.util.EnumMap;
import java.util.Map;
public class DayCount {
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
public static void main(String[] args) {
Map<Day, Integer> counts = new EnumMap<>(Day.class);
for (String w : "MONDAY FRIDAY MONDAY SUNDAY".split(" ")) counts.merge(Day.valueOf(w), 1, Integer::sum);
System.out.println(counts);
}
}
$ java DayCount.java
{MONDAY=2, FRIDAY=1, SUNDAY=1}
merge works on an EnumMap like any other map, and the result comes out in week order.