Nothing ventured, nothing gained

a blog by Marc Chung

Collect with Ruby and Java

»

by Marc Chung

From the java and ruby part of the brain.

Recently I’ve been doing a lot of Ruby programming. I’ve done a lot of Java and C++ in the past, so it’s always interesting to compare styles and design techniques between languages.

Ruby has closures which, amongst other things, allows the language to operate on collections in a compact and concise manner. For instance, take for-loops. Ruby has for-loops, but you rarely use them.

Take the following list:

list = ["matz", "eats", "sushi"]

Instead of looping over it with:

for i in list
  puts i
end

A common Ruby idiom is:

list.each {|i| puts i}

The power, in this case, comes from expressiveness balanced with brevity.

There are a whole bunch of other collection methods such as select, find, and collect.

For a bunch of reasons, my favorite method is collect.

Say you had a collection of User objects with the method first_name. To get a list of first names, you could do something like this:

users = [...]
first_names = []
for u in users
  first_names << u.first_name
end

But as before, idiomatic Ruby looks like:

first_names = users.collect { |u| u.first_name }

Again, brevity with expressiveness.

Just for comparison, let’s try and do this in Java6-land. Assuming a User object with the method getFirstName(), one easy approach might look like:

List<User> users = ...
List<String> first_names = new ArrayList<String>();
foreach(User user : users) {
  first_names.add(user.getFirstName())
}

But what if we wanted to call User.getLastName(), or User.getAge() which returns a totally different type. Without closures, the only approach is to duplicate the same for-loops over and over again, each with a different method call and return type.

Is a closures-like approach possible in Java6? Let’s give it a shot.

First, since Java6 doesn’t come with closures, you’re going to have to model one.

public interface Closure<R, T> {
  public R call(T t);
}

A closure in this case is simply a function that accepts an object, type T, and returns an object, type R. Seeing a concrete implementation will help clear things up.

A User object, which we’ll skip. Just keep in mind that it has a method called getFirstName() which returns a String and getAge() which returns an Integer.

An actual closure implementation which looks like

Closure<String, Person> nameColl = new Closure<String, Person>() {
    public String call(Person t) {
        return t.getFirstName();
    }
};

And finally, the Collect method:

public static <T, R> List<R> collect(List<T> list, Closure<R, T> clo) {
    List<R> res = new ArrayList<R>();
    for (final T t : list) {
        res.add(clo.call(t));
    }
    return res;
}

A test harness:

List<User> list = new ArrayList<User>();
list.add(new User("marc", 26);
list.add(new User("michelle", 25);
List<String> results = collect(list, nameColl); => ["marc", "michelle"]

To get a list of ages, your closure implementation would look like this:

Closure<Integer, User> ageColl = new Closure<Integer, User>() {
    public Integer call(User t) {
        return t.getAge();
    }
};

A test harness:

List<Integer> results = collect(list, ageColl); => [26, 25]

As you can see the Java solution is much longer. In Java, more Typing means more typing.

An open challenge: Is a more concise approach possible in Java6?

Frustrated with all that typing? Don’t worry, there is ongoing work to make closures part of the Java programming language.

Want to know more?

I'm Marc Chung, and you're reading Nothing ventured, Nothing gained, a blog about building beautiful software. I'm the founder of OpenRain Software, a web design and development company located in Arizona, where I make millions of users happy by building breathtaking software with brilliant people.

Presentations, Talks, Etc