Apr/107
Google collections and JavaBeans
In this article I want to show you some useful techniques to simplify the process of using google collections with your JavaBeans.
Examples will be based around the manipulation of the following customer data.
public class Customer { private String name; public Customer(String name) { this.name = name; } public String getName() { return name; } } private List<Customer> customers = newArrayList( new Customer("shanon"), new Customer("fred") );
Extracting a list of property values
If you want to extract a list of names from your customers you might start with an implementation like this:
import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Lists.transform; ... @Test public void listCustomerNames() { Function<Customer, String> toName = new Function<Customer, String>() { public String apply(Customer customer) { return customer.getName(); } }; List<String> expected = newArrayList("shanon", "fred"); assertEquals(expected, transform(customers, toName)); }
It's very likely that you will be writing this sort of code a lot. You might need to extract a list of customer emails for a marketing campaign. You might need to extract a list of customer ages so you can calculate the average age of your customers... You get the idea.
A lot of the time your simply going to be extracting a property value from a JavaBean. If your not scared of using a little reflection you can eliminate a lot of this tedious code and still maintain a reasonable level of type safety.
compacted-collections provides a simple reflection based solution that you can use, however it's not difficult to write your own.
import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Lists.transform; import static com.compactcode.Functions.toPropertyValue; ... @Test public void listCustomerNamesUsingReflection() { Function<Customer, String> toName = toPropertyValue("name"); List<String> expected = newArrayList("shanon", "fred"); assertEquals(expected, transform(customers, toName)); }
Filtering a list on a property value
If you want to filter a list of customers by name you might start with an implementation like this:
import static com.google.common.collect.Iterables.find; ... @Test public void findCustomerNameEqualToFred() { assertEquals("fred", find(customers, nameEqualTo("fred")).getName()); } private Predicate<Customer> nameEqualTo(final String value) { return new Predicate<Customer>() { public boolean apply(Customer customer) { return customer.getName().equals(value); } }; }
Inside this predicate we are converting our customer to a name and then performing an equals on that name. But again this is the sort of code your going to be writing a lot so we need something that's a bit more reusable.
Google collections provides a handy way to for us to combine existing functions and predicates in interesting ways that help promote code re-use.
Using composition we can combine our existing function for mapping customers to names with a predicate for matching strings like this:
import static com.google.common.base.Predicates.compose; import static com.google.common.base.Predicates.equalTo; ... private Predicate<Customer> nameEqualTo(String value) { return compose(equalTo(value), toName); }
If you are using compacted-collections you can achieve a similar result with the convenience methods that do the composition for you:
import static com.compactcode.FluentList.fluent; import static com.google.common.base.Predicates.equalTo; ... @Test public void findCustomerNameEqualToFred() { assertEquals("fred", customers.find(toName, equalTo("fred")).getName()); }
Or, if you wanted to avoid using composition altogether you could just use a Hamcrest matcher that achieves the same result:
import static com.compactcode.FluentList.fluent; import static org.hamcrest.beans.HasPropertyWithValue.hasProperty; import static org.hamcrest.core.IsEqual.equalTo; ... @Test public void findCustomerNameEqualToFred() { Matcher<Customer> isFred = hasProperty("name", equalTo("fred")); assertEquals("fred", customers.find(isFred).getName()); }
Well, that's all I've got for now. I don't really feel like I've done these topics justice, especially composition but perhaps it is enough of a spark to start a fire
April 21st, 2010
Combine the examples with bindgen and it will be gold
April 22nd, 2010
Actually, that would work really well
April 23rd, 2010
FWIW, I don’t think these kinds of extremely un-typesafe shortcuts belong in good Java code. Use Ruby if you want Ruby; Java if you want Java.
That’s just my opinion. Sorry to be a naysayer.
April 23rd, 2010
I’ll accept that. It’s certainly not to everyone’s taste.
My code is going to be well tested either way so I’m not too fussed about how it is implemented.
April 26th, 2010
KB, if you use bindgen, you can have the property references be checked at compile time. Only downside is you won’t be able to refactor those property references via tools. At least not yet.
May 17th, 2010
Hey Shannon, you can have your cake and eat it too with Scala. This is typesafe and compact:
User.find(By(User.email, “foo@bar.com”)) // legal
User.find(By(User.birthday, new Date(“Jan 4, 1975″))) // legal
User.find(By(User.birthday, “foo@bar.com”)) // compiler error
May 18th, 2010
I have been putting off learning Scala for a while now but I think its getting to the point where I need to go and get my hands dirty