Skip to main content

A Short Primer on Java Enums - Part 2

Posted by johnsmart on May 19, 2008 at 3:06 PM PDT

In the first part of this article, we looked at the basics of how to use enums in Java 5. In this part, we look at some more advanced use cases, including how to use enums with Hibernate.

Adding extra fields to enums

Enums are implemented as fully-blown classes, so they can also have attributes. Here, for example, we add a "code" attribute to the Status enum, representing a single-character code used by some backend system that we need to talk to:

    public enum Status {
Open("O"), Closed("C"), Reopened("R")
       
        final String code;
       
        Status(String code) {
            this.code = code;
        }
    };

You can use the attribute as you would for any other class, eg:

        Status[] values = Status.values();
        for(Status statue : values) {
            System.out.println(status.code + " - " + status);
        }

Which would produce:

O - Open
C - Closed
R - Reopened

You can also add extra methods. For example, a reader of the first part of this article suggested adding a boolean attribute to indicate whether a status value was 'open' (assuming that the 'Reopened' status implies that the issue is 'open'). You can then add an isOpen() method to determine whether a particular enum value represents an open issue. This is a good example, so I've included it here:

    public enum Status {
        Open(true), Closed(false), Reopened(true);
       
        private boolean open;

        private Status(boolean open) {
        this.open=open;
}

        public boolean isOpen() {
        return open;
        }
    };

Adding methods to enums is not limited to simple getters. The following example shows how to do a simple range comparison . We define an enum containing a certain number of price brackets, as well as a static method, getPriceBracket(), that returns the enum value corresponding to a given price bracket:

    public enum PriceBracket {
       
        Low(0,100), Medium(101,1000), High(1000, 10000);
              
        private int minPrice;
        private int maxPrice;
       
        private PriceBracket(int min, int max)   {
            this.minPrice = min;
            this.maxPrice = max;           
        }
       
        public static PriceBracket getPriceBracket(int price) {
            for (PriceBracket bracket : PriceBracket.values()) {
                if (price >= bracket.minPrice && price <= bracket.maxPrice) {
                    return bracket;
                }
            }               
            return null;
        }
    }

Them you can use the getPriceBracket() method to find the corresponding enum value for a given price:

        assertThat(PriceBracket.getPriceBracket(50), is(PriceBracket.Low));
        assertThat(PriceBracket.getPriceBracket(500), is(PriceBracket.Medium));
        assertThat(PriceBracket.getPriceBracket(5000), is(PriceBracket.High));
        ...

Of course, in a real application, this sort of data would probably need to be more flexible, and would be better placed in a database, but you get the idea.

Persisting enums with Hibernate

If you are using Hibernate for your database persistence layer, you are in luck. Persisting enumerated types with Hibernate is easy, though you should be wary of the default implementation. Indeed, by default, Hibernate will store the enum as a integer value, using the ordinal of the enum. This is a really bad idea, as Josh Bloch also points out at length - if you insert a new enum value to your list, for example, the existing database values may become incorrect. Instead, you should use the @Enumerated annotation with the EnumType.STRING option, as shown here:

    @Enumerated(EnumType.STRING)
    private Status status;

This will store the String form of the enum, which is hopefully a bit more stable.

So you can use enums very effectively as a quick way to implement what would have traditionally been done using code tables. There are a few caveats, however. If you need to be able to sort enum values directly in a database query, it is probably better to represent the enumerated type as a separate class, as the natural order of an enum field is not represented in the corresponding database table.

Adding user-readable labels for your enums

By default, the String representation of your enum will be the String version of the name of the enum. If you want a more user-friendly label, you can override the toString() method, as shown here:

    public enum Importance {

        Low {
            @Override
            public String toString() {
                return "Low Priority";
            }      
        },
        Medium {
            @Override
            public String toString() {
                return "Medium Priority";
            }      
        },
        High {
            @Override
            public String toString() {
                return "High Priority";
            }      
        },
        Critical {
            @Override
            public String toString() {
                return "Critical Priority";
            }      
        }
    };

Now, it is easy to generate a list of readable labels. For example, the following code will list the String equivalents of each value in the list:

        Importance[] values = Importance.values();
        for(Importance importance : values) {
            System.out.println(importance);
        }

This will generate the following:
Low Priority
Medium Priority
High Priority
Critical Priority

Sometimes it is still useful to be able to obtain the original name of the enum. You can do this using the name() method. For example, the following code will produce the original enum name as well as the user-friendly version:

        Importance[] values = Importance.values();
        for(Importance importance : values) {
            System.out.println(importance.name + " - " + importance);
        }

This will generate the following:
Low - Low Priority
Medium - Medium Priority
High - High Priority
Critical - Critical Priority

Conversely, you can use the valueOf() method to convert from the name of an enum to the actual enum value:

	assertThat(ActivityImportance.valueOf("Low"), is(Low));

When not to use enums

Enums are cool, but don't go overboard. Enums are good for light-weight, predefined lists of values. They are stored in the database as a String value, not as a separate table. The list of available values is defined at compile-time. So enums are good for stable, type-safe lists of values, but not good if you want to add new values on-the-fly while the application is running.

Comments

Trying to use an enum class (StatusEnum.java) with ejb3. When I put the EnumType.STRING annotation on my status variable (private STATUS status) the @Entity annotation compiles with an error saying "Entity "Topic" has no Id or EmbeddedId". Any idea what I'm doing wrong? The local class implements Serializable and has a private static final long serialVersionUID...

hiberante enum

I have an enum class as below public enum Unit implements Serializable { Kilogram("Kilogram", "kg", 1.0), // base unit Gram("Gram", "g", UnitConversion.GRAM_TO_KILOGRAM_FACTOR), } and i declared in my entity class as below @Column(length = 50) @Enumerated(EnumType.STRING) private Unit unit; but however when i save record in databse , record is getting saved as Kilogram but i want to save value as kg Kilogram("Kilogram", "kg", 1.0), // base unit I tried adding toString method to my enum class as follows @Override public String toString() { return symbol; } but doesnt work, is there any solution, please advise i even tried Kilogram ("Kilogram", "kg", 1.0) { @Override public String toString() { return "kg"; } }, // base unit Gram("Gram", "g", UnitConversion.GRAM_TO_KILOGRAM_FACTOR) { @Override public String toString() { return "g"; } } but saving value as Kilogram in databse, please advise if there is a solution if i want to save as kg