Enumerations: Why? When?
Asked Answered
M

7

7

I am an almost-graduating computer science student, and throughout my coding career, I've found very few instances where enumerations, except for canonical cases such as representing the faces of a standard deck of cards, are used.

Do you know of any clever ways that enums are used in everyday coding?

Why are enumerations so important and in what situations should one be able to identify that building an enumeration is the best approach?

Myatt answered 29/7, 2010 at 13:43 Comment(5)
"Do you know of any clever ways that enums are used in everyday coding?" - it's not about being clever; it's about having code that is readable, writable, maintainable, reusable, etc. enum/EnumMap/EnumSet combo enables all of this wonderfully. Abstraction is good, and enum is a powerful abstraction, even more so in Java than in C#/C++.Khudari
@Khudari Can you tell me a practical example for using EnumMap/EnumSet?Myatt
@polyg: As I mentioned below, I'm not an expert in Java. Why are enums more powerful in Java than C++/C#?Edytheee
@James: Java enum instances are really full-blown objects that can have fields, constructors and methods (and you can even override the methods differently for each instance).Nutritious
+1 for everybody who loves enums as much as i do.Tweeter
K
28

These are the main arguments for enum, EnumMap, and EnumSet by short examples.

The case for enum

As of Java 6, java.util.Calendar is an example of a messy class that could've benefited a lot from using enum (among other improvements).

Currently Calendar defines the following constants (among many others):

// int constant antipattern from java.util.Calendar
public static final int JANUARY = 0;
public static final int FEBRUARY = 1;
...
public static final int SUNDAY = 1;
public static final int MONDAY = 2;
...

These are all int, even though they obviously represent different conceptual entities.

The following are some serious consequences:

  • It's brittle; care must be taken to assign different numbers whenever needed.
    • If by mistake we set MONDAY = 0;, SUNDAY = 0;, then we have MONDAY == SUNDAY
  • There is no namespace and no type-safety, since everything is just an int:
    • We can setMonth(JANUARY), but we can also setMonth(THURSDAY) or setMonth(42)
    • Who knows what set(int,int,int,int,int,int) (a real method!) does!

By contrast, we could have something like this instead:

// Hypothetical enums for a Calendar library
enum Month {
   JANUARY, FEBRUARY, ...
}
enum DayOfWeek {
   SUNDAY, MONDAY, ...
}

Now we never have to worry about MONDAY == SUNDAY (it can never happen!), and since Month and DayOfWeek are different types, setMonth(MONDAY) does not compile.

Additionally, here are some before-and-after codes:

// BEFORE with int constants
for (int month = JANUARY; month <= DECEMBER; month++) {
   ...
}

Here we're making all sorts of assumptions, e.g. JANUARY + 1 == FEBRUARY, etc. On the other hand, the enum counterpart is both more concise, more readable, and makes less assumptions (and therefore less chance for bugs):

// AFTER with enum
for (Month month : Month.values()) {
   ...
}

The case for instance fields

In Java, enum is a class that has many special properties, but a class nonetheless, allowing you to define instance methods and fields if necessary.

Consider the following example:

// BEFORE: with int constants
public static final int NORTH = 0;
public static final int EAST  = 1;
public static final int SOUTH = 2;
public static final int WEST  = 3;

public static int degreeFor(int direction) {
   return direction * 90; // quite an assumption!
   // must be kept in-sync with the int constants!
}

//...
for (int dir = NORTH; dir <= WEST; dir++) {
   ... degreeFor(dir) ...
}

On the other hand, with enum you can write something like this:

enum Direction {
   NORTH(0), EAST(90), SOUTH(180), WEST(270);
   // so obvious! so easy to read! so easy to write! so easy to maintain!

   private final int degree;
   Direction(int degree)      { this.degree = degree; }
   public int getDegree()     { return degree; }
}

//...
for (Direction dir : Direction.values()) {
   ... dir.getDegree() ...
}

The case for instance methods

Consider the following example:

static int apply(int op1, int op2, int operator) {
   switch (operator) {
      case PLUS  : return op1 + op2;
      case MINUS : return op1 - op2;
      case ...
      default: throw new IllegalArgumentException("Unknown operator!");
   }
}

As shown in previous example, enum in Java can have instance methods, but not only that but each constant can have its own specific @Override as well. This is shown in the following code:

enum Operator {
    PLUS  { int apply(int op1, int op2) { return op1 + op2; } },
    MINUS { int apply(int op1, int op2) { return op1 - op2; } },
    ...
    ;
    abstract int apply(int op1, int op2);
}

The case for EnumMap

Here's a quote from Effective Java 2nd Edition:

Never derive a value associated with an enum from its ordinal(); store it in an instance field instead. (Item 31: Use instance fields instead of ordinals) It is rarely appropriate to use ordinals to index arrays: use EnumMap instead. The general principle is that application programmers should rarely, if ever, use Enum.ordinal. (Item 33: Use EnumMap instead of ordinal indexing)

Essentially where as before you may have something like this:

// BEFORE, with int constants and array indexing
Employee[] employeeOfTheMonth = ...

employeeOfTheMonth[JANUARY] = jamesBond;

Now you can have:

// AFTER, with enum and EnumMap
Map<Month, Employee> employeeOfTheMonth = ...

employeeOfTheMonth.put(Month.JANUARY, jamesBond);

The case for EnumSet

Power of two int constants are often used e.g. in C++ to denote bit sets. This relies on bitwise operations. An example may look something like this:

public static final int BUTTON_A = 1;
public static final int BUTTON_B = 2;
public static final int BUTTON_X = 4;
public static final int BUTTON_Y = 8;

int buttonState = BUTTON_A | BUTTON_X; // A & X are pressed!

if ((buttonState & BUTTON_B) != 0) {   // B is pressed...
   ...
}

With enum and EnumSet, this can look something like this:

enum Button {
  A, B, X, Y;
}

Set<Button> buttonState = EnumSet.of(Button.A, Button.X); // A & X are pressed!

if (buttonState.contains(Button.B)) { // B is pressed...
   ...
}

References

See also

  • Effective Java 2nd Edition
    • Item 30: Use enum instead of int constants
    • Item 31: Use instance fields instead of ordinals
    • Item 32: Use EnumSet instead of bit fields
    • Item 33: Use EnumMap instead of ordinal indexing

Related questions

Khudari answered 29/7, 2010 at 14:8 Comment(4)
I kind of rushed it near the end there; I'll revise this answer from feedback if others feel like there's some parts that need improvements.Khudari
This is now assigned as the accepted answer for this question. Your post is concise and was exactly what I was looking for. Thank you.Myatt
In your instance field case, I believe you meant to write case MINUS : return op1 - op2; (You had a + operator before)Myatt
@danyim: Yes, thanks for catching that. I'll fix it tomorrow along with other edits and improvements I can think of.Khudari
E
7

What you would define as String / int constants before Java 1.5, you should now define as enums. For example, instead of having:

public class StatusConstants {
   public int ACTIVE = 1;
   public int SUSPENDED = 2;
   public int TEMPORARY_INACTIVE = 3;
   public int NOT_CONFIRMED = 4;
}

You now have a safer and more developer-friendly:

public enum Status {
   ACTIVE, SUSPENDED, TEMPORARY_INACTIVE, NOT_CONFIRMED
}

Same goes for anything that has more than one options, like statuses, street types (str., boul., rd., etc), user access levels (administrator, moderator, regular user), etc. etc.

Check this for more examples and explanations.

Exclude answered 29/7, 2010 at 13:47 Comment(1)
Isn't that an isolated case, where I have a class consisting of only defining constants?Myatt
E
5

Consider some simple code:

without enums:

 int OPT_A_ON = 1;
 int OPT_A_OFF = 0;

 int OPT_B_ON = 1;
 int OPT_B_OFF = 0;

 SetOption(OPT_A_ON, OPT_B_OFF);    // Declaration: SetOption(int, int)

That look fine, right? Except SetOptions() wants Option B first and then Option A. This will go through the compiler just fine, but when run, sets the options backwards.

Now, with enums:

  enum OptA {On =1, Off = 0};
  enum OptB {On =1, Off = 0};

  SetOption(OptA.On, OptB.Off);// Declaration: SetOption(OptB, OptA)

Now we make the same error, but this time, since enums are different types, the mistake is caught by the compiler.

(NOTE: I'm not really a Java programmer, so please forgive minor syntax errors)

Edytheee answered 29/7, 2010 at 13:57 Comment(1)
The enum syntax in your sample is off (and it's not just a minor syntax error): enum values in Java are full-blown objects and not just a fancy shortcut for integers, so you can't simply define their integer value like this. Simply remove that assignment and your code will be correct.Bur
A
3

I think you're talking about Enumerations, not Enumerators.

Enumerations are a good fit for anywhere in your application where you might otherwise use "magic strings" or "magic numbers".

The easiest area to understand their use is in File Access. Each file access mode (Read, Write, Append) is represented in the Enumeration. If you were using "magic numbers", you might have something that looks like:

public static class Constants
{
    public static final int FILEMODE_READ = 1;
    public static final int FILEMODE_WRITE = 2;
    public static final int FILEMODE_APPEND = 3;
}

When you could express the intent much more clearly and succinctly using an Enumeration:

public enum FileMode
{
    Read,
    Write,
    Append
}
Aurify answered 29/7, 2010 at 13:46 Comment(0)
B
2

Default answer: all Java developer should definitively read Effective Java Second Edition. There is a chapter about enum

Bencher answered 29/7, 2010 at 13:47 Comment(1)
Scan through Josh Bloch's Effective Java Reloaded talk from Devoxx'08 parleys.com/d/1411. He gives excellent coverage of the importance of using enums in a variety of scenarios. Unfortunately many of the slides are missing in the video, but the talk is great.Swound
V
2

Enumerations are perfect for representing the state/status of something. I often use enums such as:

public enum PlayerState {
    WALKING, STANDING, JUMPING...
}

There's always a balance to be struck between enumerations and derived classes. To give a simple example of this, consider a Cat class. Cats have different levels of hostility. So, we could make derived classes HostileCat, FriendlyCat, etc. However, there are also different types of cats, Lions, Tigers, etc. Suddenly, we have a whole bunch of Cat Objects. So, an alternative solution is to provide a Hostility enumeration in the Cat class, which reduces the number of derived classes and overall complexity.

Vimineous answered 29/7, 2010 at 13:57 Comment(1)
Thanks for your input. That example really helped me see what you meant by the "balance".Myatt
N
1

As Manuel Selva suggested, read Effective Java Second Edition, but also read the singleton section. Using enum is the easiest way to have a clean singleton object.

Natalianatalie answered 29/7, 2010 at 13:57 Comment(1)
I used the enum singleton for the first time yesterday. Saves on a lot of error-prone boilerplate.Swound

© 2022 - 2024 — McMap. All rights reserved.