Gary has posted 1 posts at DZone. View Full User Profile

Neater Java

08.14.2008
| 9189 views |
  • submit to reddit

In Java 5, as all good Java developers will know, Sun introduced the enum type. Since I’m sure anyone who is interested in what will follow knows that already, I won’t bore you with the details but there’s some decent historical information on Sun’s reasons available. I’m going to be talking a little bit about operations on enums, so let’s get started by defining one:

public enum OrderState{
PENDING, CHECKING_CREDIT, PROCESSING, SHIPPED, RECEIVED, CANCELLED, RETURNED
}

Each of those items in the enumeration represent some state that an order can be in; an order being anything you can buy from an online store. We’re also going to have a variable called currentState which will hold one of these enumerated items. Let’s not think too hard about how accurate the model is, it’s really just there as a simple example.

Now imagine that one of our requirements is to enable a button if the currentState is one of several possible states, but not in any other state. For example, the “Cancel” button is enabled while the current state is PENDING, PROCESSING, or CHECKING_CREDIT. We’re doing a containment check, a pretty common operation. The standard Java approach would be:

if(currentState.equals(Order_State.PENDING)|| currentState.equals(Order_State.PROCESSING)|| currentState.equals(Order_State.CHECKING_CREDIT)){
    cancelButton.enable();
}

Yuck! It gets the job done but that is some verbose and ugly code. We know that there’s got to be a better way, and there is: the EnumSet class. EnumSet is a somewhat overlooked Set implementation, specifically for grouping together items from the same Enum. I’ll let you look into the details of the API, but there are some very handy methods, such as range() and complementOf(). As a bonus, they’re also very efficient. How would our example look using an EnumSet?

if(EnumSet.of(Order_State.PENDING, Order_State.PROCESSING, Order_State.CHECKING_CREDIT).contains(currentState)){
    cancelButton.enable();
}

That’s much better. Rather than a bunch of ORs that look disconnected and have a lot of repeated syntax, we state pretty clearly what we want: build a set of states, and then see whether the current state is part of that set.

There is another neater way of doing this, purely in terms of how syntactically nice it looks, though it requires a little helper function in the Enum. Add the following in() method directly to the Enum type:

public boolean in(Order_State... args){
    return EnumSet.copyOf(Arrays.asList(args)).contains(this);
}
Now, we can do our contains check like this:
if(currentState.in(Order_State.PENDING, Order_State.PROCESSING, Order_State.CHECKING_CREDIT)){
    cancelButton.enable();
}

Very tidy: we’ve got a single method call that expresses exactly what we’re aiming for in a fluent manner. Now, it does have it’s downsides like the helper function in the enum, and the fact that EnumSet is more efficient. I’d suggest, though, that in 99% of cases you’re not going to be caring about those few extra nanoseconds required to convert to a List first, and that the few seconds of developer time you save every time someone has to read and understand each version more than makes up for it.

From http://solitude.vkps.co.uk/

Published at DZone with permission of its author, Gary Fleming.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Mahmoud Gangat replied on Thu, 2008/08/14 - 3:43am

You could also do this to skip the list conversion:

 public boolean in(Order_State arg, Order_State... args){
   return EnumSet.of(arg,args).contains(this);
}

replied on Thu, 2008/08/14 - 5:48am

I guess there is no need to do that all every time (assuming the condition does not change dynamically):

 

 static EnumSet cancellableStates = EnumSet.of(OrderState.PENDING, OrderState.PROCESSING, OrderState.CHECKING_CREDIT);

 

...

 

 if(cancellableStates.contains(currentState)){  

    cancelButton.enable(); 

}

 

Jeroen van Bergen replied on Thu, 2008/08/14 - 10:10am

Why not add a little more behaviour to the OrderState to enforce that only valid state transitions are allowed:

public OrderState transitionTo(OrderState requestedState) {
switch (this) {
case PENDING: {
if (requestedState == PROCESSING || requestedState == CANCELLED) {
return requestedState;
}
//....
}
}
}

A big switch statement like this is not very pleasant to see, but using it ensures you can only perform the transitions that are valid given the state/transition model that is implemented in a single place.

Eric Jablow replied on Thu, 2008/08/14 - 10:28am

If it is important to distinguish between cancellable and non-cancellable states, make cancellable an instance variable in the enumeration:

public enum OrderState{
    PENDING(true), CHECKING_CREDIT(true), PROCESSING(true),
    SHIPPED(false), RECEIVED(false), CANCELLED(false), RETURNED(false);
    private boolean cancellable;
    private OrderState(boolean cancellable) {
        this.cancellable = cancellable;
    }
    public boolean isCancellable() { return cancellable; }
}

Torbjörn Gannholm replied on Thu, 2008/08/14 - 5:11pm

Switching behaviour on a state value is not really nice at all. Why not add behaviour to the states themselves? Although you will quickly outgrow the usefulness of enums and should look up the state pattern.

Steven Baker replied on Thu, 2008/08/14 - 6:10pm

first of all, dont write:

  1. if(currentState.equals(OrderState.PENDING) ...

you should write:

  1. if(OrderState.PENDING.equals(currentState) ...

this is to avoid possible NPEs

and why is OrderState sometimes named: Order_State???

Victor Tsoukanov replied on Fri, 2008/08/15 - 1:44am

It is just a remark, Component.enable() had been deprecated and usage of Component.setEnabled(boolean) looks better

cancelButton.setEnabled(currentState.in(Order_State.PENDING, Order_State.PROCESSING, Order_State.CHECKING_CREDIT));

 

James Selvakumar replied on Mon, 2008/08/18 - 5:17am

Nice article. Thanks for highlighting this.

christiaan_se replied on Mon, 2008/08/18 - 9:18am

Also realize that the static constructor (strangely) doesn't allow for null values or empty lists, so you might want to add a check around this:

public boolean in(Order_State... args){   
    if(args.length == 0) {
        //just to show construction of empty EnumSet, always returns false
        return EnumSet.noneOf(Order_State.class).contains(this);
    }
    return EnumSet.copyOf(Arrays.asList(args)).contains(this);   
} 

Peter Ufak replied on Wed, 2008/08/20 - 12:16am

if(EnumSet.of(Order_State.PENDING, Order_State.PROCESSING, Order_State.CHECKING_CREDIT).contains(currentState)){  

    cancelButton.enable();  

is smart. Interesting idea for me. But version vith in() method is unefficient because returns always new object (does JVM it optimize? ) . Simple if has constant object., I think.

Peter Ufak replied on Wed, 2008/08/20 - 3:44pm

I thought constant EnumSet in previous comment, naturaly.

Gauthier, Hough... replied on Thu, 2011/08/04 - 1:14pm

It’s smart enough to be able to intelligently expand a collection of beans and create a new row for each one. That can reduce your boilerplate code significantly. -Gauthier, Houghtaling and Williams

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.