home | src | pltl

The History of for Loops In Java

Lets take a quick look at the for loop.

It used to loop like this:

List<Integer> numbers = ...

for (int i = 0; i < numbers.size(); i++) {
    System.out.println(numbers.get(i));
}

for (Iterator<Integer> iterator = numbers.iterator(); iterator.hasNext(); ) {
    Integer number = iterator.next();
    System.out.println(number);
}

Since java 5 it could loop like this:

for (Integer number : numbers) {
    System.out.println(number);
}

That one lasted for quite a while.

If you used google’s guava you might have seen it loop somewhat like this:

numbers.forEach(new Consumer<Integer>() {
    @Override
    public void accept(Integer x) {
        System.out.println(x);
    }
});

In java 8, Consumer<T> is a @FunctionalInterface so we can replace it with a lambda.

numbers.forEach((Integer x) -> {
    System.out.println(x);
});

Lambdas are anonymous functions that can be passed around like objects. A function that takes as parameters, or returns, other functions is a higher-order function. In this case, forEach is a higher-order function that takes a Consumer<T> as an argument and applies it for each number in numbers.

When a lambda has only one statement in it’s body we can omit the parenthesis.

And we can also infer the type of x from the type of numbers:

numbers.forEach(x -> System.out.println(x));

When the parameter list is used in the same order in the lambda (in this case x is just passed on to println) we can replace it with a method reference:

numbers.forEach(System.out::println);

An interesting journey after all:

loop!

Wait what is that @FunctionalInterface thing you just mentioned?

An interface with only one abstract method can be marked with the @FunctionalInterface annotation and used as a lambda. But how many interfaces do you know with only one method? not many. that’s why we need default methods:

@FunctionalInterface
public interface Class<T> {
    T abstractMethod(T t);
    
    default T method(T t) {
        return t;
    }
}

A default method has a default implementation. sort of like in an abstract class.

This feature is very useful on it’s own, but combined with @FunctionalInterface it also allows us to retrofit many of the existing interfaces and use them in lambda form. so for example, Comparator<T> turns into:

Collections.sort(numbers, (o1, o2) -> o1.compareTo(o2));

All of this just to get rid of some anonymous class boilerplate?

Well no, lambdas open the door to functional programming (just a bit) and the new streams api takes full advantage of it.

We can get a Stream<T> from any Collection<T>.

Here we use a Predicate<T> to filter odd numbers:

numbers.stream().filter(n -> n % 2 == 0);

We can also map from one type to another using a Function<T>:

numbers.stream().map(n -> n.toString());

And we can reduce the stream to a single value using an accumulator and a BiFunction:

numbers.stream()
        .map(n -> n * 2)
        .reduce(0, (sum, n) -> sum += n);

Or maybe just collect it to a list again:

numbers.stream()
        .map(n -> n * 2)
        .collect(Collectors.toList());

Collectors has many useful ways of collecting a stream to all kinds of data structures:

Collectors.toSet()
Collectors.toMap()

numbers.stream()
        .map(n -> n * 2)
        .collect(Collectors.groupingBy(n -> n % 10));

It’s important to remember that:

For a good explanation of the consequences this has on performance watch this.

Anyway, here are a few more examples:

numbers.stream().anyMatch(n -> n % 3 == 0);
numbers.stream().allMatch(n -> n % 3 == 0);

numbers.stream()
        .filter(n -> n % 3 == 0)
        .findFirst();
        
numbers.stream()
        .filter(n -> n < 3)
        .distinct()
        .sorted()
        .collect(Collectors.toList());

// get highest grade out of all grades of all students
students.stream()
    .flatMap(s -> s.getGrades())
    .collect(Collectors.maxBy(Integer::compare))

Primitive streams have some neat functions too:

IntStream.of(1,2,3,4,5).count();
LongStream.of(1,2,3,4,5).sum();
DoubleStream.of(1,2,3,4,5).average();
LongStream.of(1,2,3,4,5).max();
IntStream.of(1,2,3,4,5).min();

Also, infinite streams:

Stream.iterate(1, e -> e + 1)
        .filter(e -> e % 13 == 0)
        .filter(e -> e % 21 == 0)
        .limit(5)
        .forEach(System.out::println); // => 273 546 819 1092 1365

And what is that weird method reference syntax?

Method references are nothing but a shorthand notation for lambdas that do nothing but call an existing method:

numbers.stream()
        // instance method
        .peek(System.out::println) // == x -> System.out.println(x)
        // static method
        .map(String::valueOf) // == x -> String.valueOf(x);
        // instance method of parameter
        .map(String::toString) // == x -> x.toString();
        // works with multiple params as long as they are used in the same order
        .reduce(String::concat); // == (carry, s) -> carry.concat(s)

Back up a sec

Lets compare loops again:

for (Integer number : numbers) {
    System.out.println(number);
}

numbers.forEach(System.out::println);

A shorter syntax is nice and all but there’s something else going on here. Notice that in the first example the iteration is controlled by the for loop. It’s an external iteration. In the second example it’s the collection itself that controls the iteration. It’s an internal iteration. Who cares? Well, when the iteration is internal we can benefit from polymorphism and do crazy things like this:

numbers.parallelStream().forEach(System.out::println);

Warning: do not try to parallel things without profiling.

Anything else?