There's more to Java Enums

If you find yourself using "switch" or "if-else" a lot with your enums, then you're missing out on one important feature.

There's more to Java Enums

Java's enums, being objects themselves, can have state and behavior.

Consider the following example.

public enum Coin {
    PENNY,
    NICKEL,
    DIME,
    QUARTER,
    HALF_DOLLAR,
    DOLLAR
}

You might be tempted to do the following:

public long getValueInCents(Coin coin) {
    switch (coin) {
        case PENNY: return 1;
        case NICKEL: return 5;
        case DIME: return 10;
        case QUARTER: return 25;
        case HALF_DOLLAR: return 50;
        case DOLLAR: return 100;
    }
}

public long addTwoCoins(Coin coin1, Coin coin2) {
    return getValueInCents(coin1) + getValueInCents(coin2);
}

Nothing wrong with that. However, if you start adding more and more methods around them, then you could end up with a lot of if-else or switch statements. Say, your application is a virtual vending machine, and it only accepts certain coins. You might have a method like this:

public boolean isAccepted(Coin coin) {
    return coin == Coin.DIME || coin == Coin.QUARTER;
}

Again, nothing wrong with that. But what if you could encapsulate all of the above into the enums?

Here's the same Coin enum after the makeover.

public enum Coin {
    PENNY(1, false),
    NICKEL(5, false),
    DIME(10, true),
    QUARTER(25, true),
    HALF_DOLLAR(50, false),
    DOLLAR(100, false);

    // enums can have state; these are set via the constructor
    private long valueInCents;
    private boolean isAccepted;

    private Coin(long valueInCents, boolean isAccepted) {
        this.valueInCents = valueInCents;
        this.isAccepted = isAccepted;
    }

    // enums can also have behavior

    public long getValueInCents() {
        return valueInCents;
    }

    public boolean isAccepted() {
        return isAccepted;
    }

    public long add(Coin anotherCoin) {
        return this.valueInCents + anotherCoin.valueInCents;
    }
}

Code that uses this new enum implementation simply has to call any of the public methods, e.g.:

public void attemptCoins(Coin coin1, Coin coin2) {
    if (coin1.isAccepted() && coin2.isAccepted()) {
        long totalValue = coin1.add(coin2);
        // rest of method here...
    } else {
        throw new RuntimeException("One or both coins are not accepted!");
    }
}

Each enum value can also override methods as needed. Here's a very contrived example where the DOLLAR enum throws an exception when add() is called with anything more than 25 cents. Perhaps because the maximum value supported by the application is $1.25, ha! Told you it was contrived.

public enum Coin {
    PENNY(1, false),
    NICKEL(5, false),
    DIME(10, true),
    QUARTER(25, true),
    HALF_DOLLAR(50, false),
    DOLLAR(100, false) {
        @Override
        public long add(Coin anotherCoin) {
            if (anotherCoin.valueInCents <= 25) {
                return super.add(anotherCoin);
            }
            throw new ValueOverflowException();
        }
    }

    // enums can have state; these are set via the constructor
    private long valueInCents;
    private boolean isAccepted;

    private Coin(long valueInCents, boolean isAccepted) {
        this.valueInCents = valueInCents;
        this.isAccepted = isAccepted;
    }

    // enums can also have behavior

    public long getValueInCents() {
        return valueInCents;
    }

    public boolean isAccepted() {
        return isAccepted;
    }

    public long add(Coin anotherCoin) {
        return this.valueInCents + anotherCoin.valueInCents;
    }
}