Thursday, June 26, 2008

Cheap Tricks XV - Static Collections in Java

Being back in the Java game from Ruby, makes me miss a lot of the shortcuts. Despite the crushing pain I feel whenever I recognize a 5 line block of Java code that can be reproduced with one short Ruby line, being a professional Ruby developer was overall a great experience. It has made me so lazy, I have amassed quite the little toolkit to save precious lines. Here's another cheap trick around reducing the common need for a static block populated collections (NOTE: this isn't limited to Enums - if you are only dealing with Enums, just use EnumSet.of):
public class StateMachine
{
enum State { STARTED, COMPLETED, CANCELLED, ABORTED }

public static final Set<State> FINAL_STATES;
static
{
HashSet<State> fs = new HashSet<State>();
fs.add( COMPLETED );
fs.add( CANCELLED );
fs.add( ABORTED );
FINAL_STATES = Collections.unmodifiableSet( fs );
}
// ...
}
into a trim one-liner:
import static StaticUtils.sset;
import static StateMachine.State.*;

public class StateMachine
{
enum State { STARTED, COMPLETED, CANCELLED, ABORTED }

static final List<State> FINAL_STATES = sset( COMPLETED, CANCELLED, ABORTED );
// ...
}
Here's a code for a static list creator:
public final class StaticUtils
{
public static final <T> Set<T> sset( T...objects )
{
HashSet<T> al = new HashSet<T>( objects.length );
for ( T t : objects ) al.add( t );
return Collections.unmodifiableSet( al );
}
}
As one person pointed out, you can achieve the same effect for Lists via:
import static java.util.Arrays.asList;
import static StateMachine.State.*;

public class StateMachine
{
enum State { STARTED, COMPLETED, CANCELLED, ABORTED }

static final List<State> FINAL_STATES = asList( COMPLETED, CANCELLED, ABORTED );
// ...
}
Slight modifications can be made for handling maps:
public final class StaticUtils
{
// ...
public static final <K, V> Map<K, V> smap( Pair<K, V>...pairs )
{
HashMap<K, V> hm = new HashMap<K, V>();
for ( Pair<K, V> p : pairs ) hm.put( p.k, p.v );
return Collections.unmodifiableMap( hm );
}

public static class Pair<K, V>
{
private Pair(K k, V v) { this.k = k; this.v = v; }
private K k;
private V v;
}

public static final Pair<K, V> p( K k, V v )
{
return new Pair<K, V>(k, v);
}
}
Usage is similar, but bundles keys/values into a pair, the creation of which is shortcutted by the static "p" method.
import static StaticUtils.smap;
import static StaticUtils.p;
import static StateMachine.States.*;

public class StateMachine
{
enum State { STARTED, COMPLETED, CANCELLED, ABORTED }

static final Map<State, String> INCOMPLETE = smap( p(CANCELLED,"cancel"), p(ABORTED,"abort") );
// ...
}

10 comments:

Romain Guy (Google) said...

What is wrong with simply calling State.values()? Or if you really really want a List, Arrays.asList(State.values())?

Florian Potschka said...

@romain guy:
I think the difference is, that the author created a list containing only a subset of states. With State.values() etc. you only get a list of all states.

Olve Sæther Hansen said...

What about java.util.EnumSet.of(COMPLETED, CANCELLED, ABORTED);
Then you won'need to create your static utils at all.
This if you find a Set sufficient.
Also EnumSet.complementOf(EnumSet.of(STARTED) will give you the same collection.

I use the EnumSet alot and with static-imports it can be quite terse as well:
Collection < State > FINAL_STATES = unmodifiableCollection(complementOf(of(STARTED)));
of
Collection < State > FINAL_STATES = unmodifiableCollection(of(COMPLETED, CANCELLED, ABORTED));

Casper Bang said...

Cool tricks. I second the EnumSet tip, you can do remarkable good state modelling with Enum and EnumSet - use it all the time.

Matt said...

Yeah, helper methods like this are pretty much essential to help cut down on verbiage in Java. Google Collections has some of this stuff, e.g.

ImmutableList.copyOf

Anonymous said...

Whenever I code or find a cool helper method that I find useful I stick it into the Spiffy Framework. Everyone can join in. Feel free to add stuff, or even copy-paste inherit from the library into your code base

http://spiffyframework.sourceforge.net/

-kasper

Eric said...

Olve:
Yes, I concur using EnumSet wherever possible. I suppose the fact that my example uses Enums means it's use is strictly for enums - which it's not.

I actually injected this pattern into an old framework that was using ints and chars as static values.

But very good tip!

Anonymous said...

You can use Arrays.asList(T ...) instead.

David said...

By the way, Arrays.asList already creates an unmodifiable list, so no use in writing unmodifiableList(asList(...))

Eric said...

Fantastic! In light of this, I've just removed the list thing altogether - though I still think the Set and Map versions are useful.

Interestingly though, the API for asList must have changed between java 4 and 5... as it stands now, it would seem that all code with asList(array) would break. I'm surprised they were cool with that.