Monday, June 28, 2010

How to group by objects in java... in a simple way ?

Sometime it is very useful to group objects by some criteria.

We have a collection of Person we want to group by their Age.

public class Person {
    public String getName() {...}
    public int getAge() {...}
}

The first method we can use is the following :

Collection<Person> persons = ...;
Collection<Person>  personsAgeIs20 = new ArrayList<Person>();
for (Person person : persons) {
    if (person.getAge() == 20) {
         personsAgeIs20.add(person);
    }
}

Again, using google-collections, the magic class is Multimaps

Grouping Person by Age is as simple as this :

Collection<Person> persons = ...;
Map<Integer, Person> personsGroupByAge = Multimaps.index(persons, new Function<Person, Integer>() {
    public Integer apply(Person person) {
        return Integer.valueOf(person.getAge()); // Integer.valueOf because I don't like autoboxing :-p
    }
});
List<Person> personsAgeIs20 = personsGroupByAge.get(Integer.valueOf(20));

Ok, you tell me, same amout of line, why bother use Multimaps ? Because you don't code the grouping, you code the grouping logic only. Plus, with the first solution we have only grouped by 20, not by other ages. So, the Multimaps way is far more powerful and evolutive.

Tuesday, June 22, 2010

unit test and new Date()

A lot of code which use new Date() or System.currentTimeMillis() is quite impossible to unit test.

A too-current-in-every-day-life example method :

public class SomeClass {
    public String currentDateAsString() {
        return new SimpleDateFormat().format(new Date(), "yyyyMMdd");
    }
}

The corresponding unit test will not work for so long :

@Test
public void testCurrentDateAsString() {
    Assert.assertEquals("20100622", new SomeClass().currentDateAsString());
}

It will fail tomorrow ;-)

If you own the code, you should use a Date provider. Using library such as google collection (now guava), you can simply use a Supplier.

public class SomeClass {
    public SomeClass(String pattern, Supplier dateSupplier) {
        this.pattern = pattern;
        this.dateSupplier = dateSupplier;
    }

    public String currentDateAsString() {
        new SimpleDateFormat().format(this.dateSupplier.get(), this.pattern);
    }

    private final String pattern;
    private final Supplier dateSupplier;

}

Now the unit test will be much nicer  and more it follows the Law of Demeter :

@Test
public void testCurrentDateAsString() {
    Supplier dateSupplier = Suppliers.ofInstance(newDate(22,6,2010));
    Assert.assertEquals("20100622", new SomeClass("yyyyMMdd", dateSupplier).currentDateAsString());
}

*newDate may be a static date factory method you wrote, it is here for readability.

If the code come from scary legacy code, you should use jmockit or powermock (see Can I mock static methods).