views:

375

answers:

4

I have a question about simplifying some Collection handling code, when using Google Collections.

I've got a bunch of "Computer" objects, and I want to end up with a Collection of their "resource id"s. This is done like so:

Collection<Computer> matchingComputers = findComputers();
Collection<String> resourceIds = 
    Lists.newArrayList(Iterables.transform(matchingComputers, new Function<Computer, String>() {
    public String apply(Computer from) {
        return from.getResourceId();
    }
}));

Now, getResourceId() may return null (and changing that is not an option right now), yet in this case I'd like to omit nulls from the resulting String collection.

Here's one way to filter nulls out:

Collections2.filter(resourceIds, new Predicate<String>() {
    @Override
    public boolean apply(String input) {
        return input != null;
    }
});

You could put all that together like this:

Collection<String> resourceIds = Collections2.filter(
Lists.newArrayList(Iterables.transform(matchingComputers, new Function<Computer, String>() {
    public String apply(Computer from) {
        return from.getResourceId();
    }
})), new Predicate<String>() {
    @Override
    public boolean apply(String input) {
        return input != null;
    }
});

But this is hardly elegant, let alone readable, for such a simple task! In fact, plain old Java code (with no fancy Predicate or Function stuff at all) would arguably be much cleaner:

Collection<String> resourceIds = Lists.newArrayList();
for (Computer computer : matchingComputers) {
    String resourceId = computer.getResourceId();
    if (resourceId != null) {
        resourceIds.add(resourceId);
    }
}

Using the above is certainly also an option, but out of curiosity (and desire to learn more of Google Collections), can you do the exact same thing in some shorter or more elegant way using Google Collections?

+2  A: 

Firstly, I'd create a constant filter somewhere:

public static final Predicate<Object> NULL_FILTER =  new Predicate<Object>() {
    @Override
    public boolean apply(Object input) {
            return input != null;
    }
}

Then you can use:

Iterable<String> ids = Iterables.transform(matchingComputers,
    new Function<Computer, String>() {
        public String apply(Computer from) {
             return from.getResourceId();
        }
    }));
Collection<String> resourceIds = Lists.newArrayList(
    Iterables.filter(ids, NULL_FILTER));

You can use the same null filter everywhere in your code.

If you use the same computing function elsewhere, you can make that a constant too, leaving just:

Collection<String> resourceIds = Lists.newArrayList(
    Iterables.filter(
        Iterables.transform(matchingComputers, RESOURCE_ID_PROJECTION),
        NULL_FILTER));

It's certainly not as nice as the C# equivalent would be, but this is all going to get a lot nicer in Java 7 with closures and extension methods :)

Jon Skeet
I'd call it NOT_NULL_FILTER, personally. :) And there's already a static method for this in the Predicates class (see my answer).
Cowan
@Cowan: It depends on how you treat "filter" - you could argue it filters *out* the nulls. It's a general pain on the naming front. However, I think it's pretty obvious what it's going to do, because the reverse would be silly :) Good call on the Predicates method though.
Jon Skeet
+11  A: 

There's already a predicate in Predicates that will help you here -- Predicates.notNull() -- and you can use Iterables.filter() and the fact that Lists.newArrayList() can take an Iterable to clean this up a little more.

Collection<String> resourceIds = Lists.newArrayList(
  Iterables.filter(
     Iterables.transform(matchingComputers, ... your Function ...),
     Predicates.<String>notNull();
  )
);

If you don't actually need a Collection, just an Iterable, then the Lists.newArrayList() call can go away too and you're one step cleaner again!

I suspect you might find that the Function will come in handy again, and will be most useful declared as

public class Computer {
....
    public static Function<Computer, String> TO_ID = ...;
}

which cleans this up even more (and will promote reuse).

Cowan
Nice - not sure why I'd never spotted that Predicates method before...
Jon Skeet
Excellent advice, thank you! Using Predicates.notNull() and putting the Function in a constant indeed clarify the code a lot.
Jonik
@Cowan: Great :). When I use a fonction as a transform, I like isolate it in a static method and naming it intoXXX(), I find it easy to read. In this case, it could be : transform(matchingCompters, intoResourceId()).
Sylvain M
A: 

It's not fancy, but

resourceIds.remove(null);

will remove all the nulls from the collection. And it's probably a lot quicker than using predicates other higher level constructs.

David Roussel
Hmm, actually, it doesn't remove all nulls. See Collection.remove() Javadoc: "Removes a **single instance** of the specified element from this collection". If a Set was used for resourceIds, then this would indeed work.
Jonik
You mean resourceIds.removeAll(Collections.singleton(null)).
Kevin Bourrillion
Also, don't be so sure about "quicker". Your method involves copying everything into a mutable collection, then modifying that collection. The functional approach involves only simple immutable instances, which are practically invisible to the garbage collector.
Kevin Bourrillion
In the first example resourceIds is created by Lists.newArrayList() which according to the javadocs (http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/collect/Lists.html) returns a mutable list. So resourceIds.remove(null); should work fine, right?
David Roussel
Nope, it doesn't work as you say - that mutable list (obviously) follows the contract defined by Collection.remove(). Do try it out with a small test program - that's really quick to do: http://www.copypastecode.com/16197/
Jonik
David Roussel
A: 

You could write your own method like so. this will filter out nulls for any Function that returns null from the apply method.

   public static <F, T> Collection<T> transformAndFilterNulls(List<F> fromList, Function<? super F, ? extends T> function) {
        return Collections2.filter(Lists.transform(fromList, function), Predicates.<T>notNull());
    }

The method can then be called with the following code.

Collection c = transformAndFilterNulls(Lists.newArrayList("", "SD", "DDF"), new Function<String, Long>() {

    @Override
    public Long apply(String s) {
        return s.isEmpty() ? 20L : null;
    }
});
System.err.println(c);
BewdyM8