Search code examples
javaguava

Guava one-liner for transforming immutable lists


I think there must be a one-liner Guava solution for transforming an immutable list into another immutable list, but I can't find it. Suppose we have the following objects:

ImmutableList<String> input = ImmutableList.of("a", "b", "c");
Function<String, String> function = new Function<String, String>() {
    @Override
    public String apply(String input) {
        return input + input;
    }
};

The transformation can be achieved like this:

Iterable<String> transformedIt = Iterables.transform(input, function);
ImmutableList<String> output = ImmutableList.<String>builder().addAll(transformedIt).build();

or like this:

List<String> transformedList = Lists.transform(input, function);
ImmutableList<String> output2 = ImmutableList.copyOf(transformedList);

but I think that there must be somewhere a performance-optimized one-liner for this kind of transformation, without intermediate objects, I just can't find it. Where is it?


Solution

  • You can simply remove your builder and inline it to get a (bit longer) one-liner

    ImmutableList<String> output =
        ImmutableList.copyOf(Iterables.transform(input, function));
    

    This is sort of optimal as the result of Iterables.transform is lazy, so no temporary list gets allocated. There are AFAIK some minor inefficiencies:

    • Allocation of a FluentIterable
    • Resizing of the array used for the result

    If you really care a lot about the speed, you could benchmark it and compare to something like

    ArrayList<String> tmp = Lists.newArrayListWithCapacity(input.size());
    Iterables.addAll(tmp, Iterables.transform(input, function));
    ImmutableList<String> output = ImmutableList.copyOf(tmp);
    

    and to a handrolled loop.

    UPDATE

    While the first approach is surely the most readable one, it incurs some allocations for array resizing and also the final shrinking to desired size. With a list of length 1234567, there are the following resizing steps:

    4 -> 7 -> 11 -> 17 -> 26 -> 40 -> 61 -> 92 -> 139 -> 209 -> 314 -> 472 -> 709 -> 1064 -> 1597 -> 2396 -> 3595 -> 5393 -> 8090 -> 12136 -> 18205 -> 27308 -> 40963 -> 61445 -> 92168 -> 138253 -> 207380 -> 311071 -> 466607 -> 699911 -> 1049867 -> 1574801

    and the final shrinking

    1574801 -> 1234567

    UPDATE 2

    As Louis and Chris said, the optimal solution is

    ImmutableList<String> output =
        ImmutableList.copyOf(Lists.transform(input, function));
    

    as it includes no array copying. This works as the result of Lists.transform is a lazy collection and ImmutableList.copyOf queries its size in order to allocate a properly sized array. Note that neither Iterables.transform nor FluentIterable are that efficient.