Tuesday, November 25, 2008

better automagic java collections

A few years ago, I started writing java code like this to automagically initialize collections of junk and stuff:

private Collection< String > values_;

public Collection< String > getValues() {
return (
null == this.values_
? this.values_ = new ArrayList<>()
: this.values_
);
}

public void setValues( Collection< String > values ) {
this.values_ = values;
}

Nowadays you can find a lot of code like this in places like JAXB generated code, etc. Where you want the consumer of the class to be able to start doing stuff like:

Wizzle wuzzle = new Wizzle();
wuzzle.getValues().add( "ok" );

But there are a few problems with this. For one thing it assumes that there is no value to having the getValues call return null. Which impacts code like:

int sum = 0;
int count = 0;
for ( Wizzle wuzzle : wuzzles ) {
if( null != wuzzle.getValues() ) {
sum += wuzzle.getValues().size;
count++;
}
}
if ( 0 != count ) {
System.out.println(
"average is "
+ ( sum / ( double ) count )
);
}

Of course, I also suddenly have created a bunch of ArrayLists I really don't need.

Now you might say: so what? And OK, but the fact is, a collection being unset (ie: null) is used in a fair number of places to perform logic or calculations.

Notice how the coolio automagic collection trick throws off my averages in this code that assumes null == wuzzle.getValues() mean "undefined."

Now I am writing (ok, generating) code like this instead:

private Collection< String > values_;

public Collection< String > getValues() {
return (
this.valuesUnSet()
? this.setNewValues()
: this.values_
);
}

public boolean valuesUnSet() {
return ( null == this.values_ );
}

protected Collection< String > setNewValues() {
return ( this.values_ = this.newValues() );
}

public Collection< String > newValues() {
return new ArrayList< String >();
}

public void setValues( Collection< String > values ) {
this.values_ = values;
}

But in order for this to really work, the code to do the average has to be changed to:

int sum = 0;
int count = 0;
for ( Wizzle wuzzle : Wuzzles ) {
if( !wuzzle.valuesUnSet() ) {
sum += wuzzle.getValues().size;
count++;
}
}
if ( 0 != count ) {
System.out.println(
"average is "
+ ( sum / ( double ) count )
);
}

The nice thing about this refactored code is that it doesn't make the assumption that "null" for a collection means uninitialized.

Instead the decision as to whether or not the Collection has been initialized is left up to the model object's implementation.

This approach removes a potentially dangerous assumption while allow automagic collections to be used without penalty.

It is a small incremental improvement on the basic design.

No comments:

Post a Comment