When you switch from Java to Groovy, one of the thing things you notice right away is how Closures affect things like sorting. As an example, lets pretend we have Things
public class Thing {
public int i1;
public String val;
public Thing(String s, int i) {
val = s;
i1 = i;
}
// for easy list printing
public String toString() {
return val +"-"+i1;
}
}
(Lets ignore the public member and all – this is just to make the code shorter)
Now lets make a list of Things in Java
List<Thing> l = new ArrayList<Thing>();
l.add(new Thing("hello", 3));
l.add(new Thing("there", 1));
l.add(new Thing("apple", 5));
l.add(new Thing("orange", 10));
and in Groovy
l = [new Thing("hello", 3),
new Thing("there", 1),
new Thing("apple", 5),
new Thing("orange", 10)];
Of course, the default way to sort in Java is fairly straightforward, though of course it doesn’t work here:
Collections.sort(l);
This won’t even compile since our Thing doesn’t implement Comparable. Groovy actually executes this no problem (though the order it sorts it in is anyone’s guess.) Running this snippet works:
println()
l.sort()
println “After Sort:”
println(l)
and prints the following result:
[hello-3, there-1, apple-5, orange-10]
After sort:
[apple-5, orange-10, hello-3, there-1]
But as you can see there is no way of knowing the sort order. So of course that is where implementing Comparable kicks in. I won’t go into that since that is basic Java and if we defined Thing as Comparable it would work as expected in Groovy as well.
Java, of course, also allows us to pass a Comparator to the sort function and this will work without changing Thing:
System.out.println(l);
Collections.sort(l, new Comparator<Thing> () {
public int compare(Thing o1, Thing o2) {
return o1.i1 - o2.i1; // sort by the int value
}
});
System.out.println("After Sorting:");
System.out.println(l);
Which will yield:
[hello-3, there-1, apple-5, orange-10]
After Sorting:
[there-1, hello-3, apple-5, orange-10]Groovy has a similar option: println(l)
l.sort([compare:{a,b-> Math.abs(a.i1) - Math.abs(b.i1) } ] as Comparator)
println "After sort:"
println(l)
l.sort { o1, o2 ->
o1.i1 - o2.i1; // sort by the int value
}
The GDK Docs describe the function as:
sort
public List sort(Closure closure)
- Sorts this Collection using the given closure as a comparator. The closure is passed each item from the collection, and is assumed to return a comparable value (i.e. an int).
- Parameters:
closure- a Closure used as a comparator.- Returns:
- a newly created sorted List
So, you pass a Closure to sort that turns the Object you want into a Comparable object. In our example if we want to sort our Things based on their int values you can just say:
l.sort { it.i1 }
Or for the String , just return it:
l.sort { i.val }This works great to sort the list in the ascending order. Using this to sort the list in reverse order takes some maneuvering. For int this is still fairly straightforward.l.sort { -1 * it.i1 }I couldn’t get a good similar trick for Strings though – so you may need to use the Comparator or just call reverse on the returned list:
l.sort{ it.val }.reverse()
As you can see the Closure thing, can really shorten up the syntax and gives more power to what you can do in less code.
One downside of the whole Closure thing is that you can’t always tell what type of Closure a function expects to receive. If you use an editor with command completion you can only see that a function wants a Closure
and you have no way of knowing how many parameters will be passed to the Closure and what the function expects the Closure to return. In this case, the Javadocs explained what is needed but that is more difficult to look at or find sometimes rather than just being obvious from a method signature. Part of this is a function of a dynamic languages and not just the Closure issue.
Another technical question is that I am also not sure how Groovy distinguishes between the Closure version of the Comparator and the straight Closure which returns the Comparable object. My current guess is that there is code in the Closure version to check the number of parameters the Closure passed in expects to receive and calls it accordingly – because straight method dispatching should send them both to the same function. I will have to check into that.
3 comments:
Thanks for this post. It helped a lot. I didn't even think of using reverse() for the String list... cool.
Glad it helped.
The reverse thing was a last second Aha I had while writing the post.
Nice post. This is what I was looking for. :)
Post a Comment