Ruby-like collection handling in verbose Java = CollectionUtils

each, select, reject, collect, detect, inject, include?, compact – translated into Java

I have compiled this little cheat sheet for those who like working with arrays in Ruby but not just for Ruby’s expressivness as a language but mostly for the idioms behind it. If you like the way Ruby (and also Smalltalk and others, but I like Ruby the best πŸ™‚ ) handles arrays but you cannot use Ruby in your projects directly — CollectionUtils from the Apache Commons-Collections library (version 3.0 and above) can maybe be the answer you are looking for. For the price of added Java’s verbosity (compared to Ruby) you can find yourself on familiar ground with your Java collections.

Cheat sheet

each

[1, 2, 3, 4].each { |x| print x }
# >> 1
# >> 2
# >> 3
# >> 4

one possibility how to translate Ruby’s each to Java

List<Integer> i = Arrays.asList(1,2,3,4);
CollectionUtils.forAllDo(i, new Closure() {
    public void execute(Object i) {
        System.out.println(i);
    }
});

select

[1, 2, 3, 4].select { |x| x % 2 == 0 }
# >> [2, 4]

one possibility how to translate Ruby’s select to Java

List<Integer> i = Arrays.asList(1,2,3,4);
System.out.println(CollectionUtils.select(i, new Predicate() {
    public boolean evaluate(Object o) {
        return (Integer)o % 2 == 0;
    }
}));

reject

digits = (1..10).to_a
digits.reject { |x| i < 5 }
# >> [5, 6, 7, 8, 9, 10]

one possibility how to translate Ruby’s reject to Java

List<Integer> i = new ArrayList<Integer>(10);
for (int j = 1; j <= 10; j++) {
    i.add(j);
}
System.out.println(CollectionUtils.selectRejected(i, new Predicate() {
    public boolean evaluate(Object o) {
        return (Integer)o < 5;
    }
}));
&#91;/sourcecode&#93;
<h4>collect</h4>

[1, 2, 3].collect { |x| x + 1 }
# >> [2, 3, 4]

one possibility how to translate Ruby’s collect to Java

List<Integer> i = Arrays.asList(1,2,3);
System.out.println(CollectionUtils.collect(i, new Transformer() {
    public Object transform(Object input) {
        return (Integer)input + 1;
    }
}));

detect

(1..100).to_a.detect { |i| i % 5 == 0 and i % 7 == 0 }
# >> 35

one possibility how to translate Ruby’s detect to Java

List<Integer> i = new ArrayList<Integer>(100);
for (int j = 1; j <= 100; j++) {
    i.add(j);
}
System.out.println(CollectionUtils.find(i, new Predicate() {
    public boolean evaluate(Object o) {
        return (Integer)o % 5 == 0 && (Integer)o % 7 == 0;
    }
}));
&#91;/sourcecode&#93;
<h4>inject</h4>

[1, 2, 3, 4, 5].inject(0) { |sum, x| sum += x }
# >> 15

Translating inject is a little tricky because of the anonymous classes involved and the final value retrieval. Here is one possible way how to translate Ruby’s inject to Java. Probably not the best way to do it but still a working solution πŸ™‚

List<Integer> i = new ArrayList<Integer>(5);
for (int j = 1; j <= 5; j++) {
    i.add(j);
}
CollectionUtils.transform(i, new Transformer() {
    int sum = 0;
    public Object transform(Object input) {
        sum += (Integer)input;
        return sum;
    }
});
System.out.println(i.get(i.size() - 1));
&#91;/sourcecode&#93;
<strong>update:</strong> A better solution, as Daniel suggested, would be to use <em>Closure</em> instead of a <em>Transformer</em> with combination with <em>CollectionUtils#forAllDo()</em>


// lets use an inner class to do the work:
private static class SumAccumulator implements Closure {
    private int sum;

    public void execute(Object o) {
        sum += (Integer)o;
    }

    public int getSum() {
        return sum;
    }
}

// then the sum accumulation would look something like this:
SumAccumulator accumulator = new SumAccumulator();
CollectionUtils.forAllDo(i, accumulator);
System.out.println(accumulator.getSum());

include?

digits = (1..10).to_a
digits.include? 5
# >> true

one possibility how to translate Ruby’s include? to Java

List<Integer> i = new ArrayList<Integer>(100);
    for (int j = 1; j <= 10; j++) {
        i.add(j);
    }
System.out.println(CollectionUtils.exists(i, PredicateUtils.identityPredicate(5)));
&#91;/sourcecode&#93;
<h4>compact</h4>

["a", null, "b", null, "c", null].compact
# >> ["a", "b", "c"]

one possibility how to translate Ruby’s compact to Java

List<String> s = new ArrayList<String>();
s.add("a");
s.add(null);
s.add("b");
s.add(null);
s.add("c");
CollectionUtils.filter(s, PredicateUtils.notNullPredicate());
System.out.println(s);

As you can see the added finger typing with ‘translated’ Ruby into Java is in orders of hundreds of percent for small examples like these. But these constructs should stay unchanged and the more business logic you have the more efficient they will get. The added bonus is in being able to work with Java collections in somewhat familiar way to Ruby’s array. You could even make your own List interface implementation with each(), select(), reject(), collect(), detect(), inject(), include(), compact() methods added to the mix. And you could end up with something like this:

class MyList extends ArrayList {
    public void each(Closure closure) {
        CollectionUtils.forAllDo(this, closure);
    }

    public MyList select(Predicate predicate) {
        return MyList(CollectionUtils.select(this, predicate));
    }

    // ... and so on ... you get the idea
}
Advertisements
7 comments
  1. Daniel De Aguiar said:

    For inject, I’d use CollectionUtils.forAllDo() instead of transform() since transform will alter the passed in list if you are not careful (Transformer.transform has a return value). The only caveat is that you cannot use an anonymous class since you want to accumulate something. In this case, the accumulator would be the Closure implementation passed into forAllDo().

  2. ytoh said:

    so for example something like this:

    Transformer t = new Transformer() {
    private int sum;

    public Object transform(Object input) {
    sum += (Integer)input;
    return sum;
    }
    };

    List i = new ArrayList(5);
    for (int j = 1; j <= 5; j++) {
    i.add(j);
    }

    CollectionUtils.forAllDo(i, ClosureUtils.asClosure(t));

    System.out.println(t.transform(0));

    where the argument to the last t.transform() call has to be a neutral element of the transform relation.

  3. Daniel De Aguiar said:

    I’d keep the Transformer out of it and instantiate a Closure instance directly. See http://gist.github.com/91233 fir a code sample.

    Alternatively, make the sum instance variable protected or public to avoid the last call to t.transform() – I think that would be cleaner.

  4. ytoh said:

    I have the last t.transform() call there because I dislike accessing instance variables directly then I have no controll over it.
    Unfortunately there is no way how to use an anonymous class then :-/. Then I would opt for adding an accessor method for the result field:

    static class SumAccumulator implements Closure {
    public int result;
    public void execute(Object arg0) {
    result += ((Integer) arg0).intValue();
    }

    public int getResult() {
    return result;
    }
    }

    What do you think?

  5. Daniel De Aguiar said:

    That’s fine, just update the example code and make the field private. I personally don’t mind the breach of encapsulation so much because the class has a very specific usage with a limited scope and would be an inner class. That being said, I’m a big fan of immutability and would not normally expose the guts of my classes.

    I was also bummed that an anonymous class couldn’t be used 😦

  6. ytoh said:

    I will update it. One question: Have you ever used Google collections? If you did, which do you like better Google collections or commons collections?

  7. Daniel De Aguiar said:

    I haven’t tried google collections yet, but it looks interesting. I’m going to give it a deeper look through.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: