Collect with Ruby and Java
»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.