If you’re programming in Java, you probably noticed the recent move towards streams, lambdas and a more functional style of writing code, greatly facilitated by Java 8’s stream API and anonymous function syntax. Great if you’re doing a clean new project on Java 8, but what about existing codebases?

Our team is currently on Java 7, but looking to move towards Java 8 in the course of the next year. To get into the habit of things, we’ve started using the concepts of functional programming in Java 7. We have been using Guava’s FluentIterable for this, which I would heartily recommend to anyone under similar circumstances.

However. The Guava API is somewhat lacking in some parts, with the team taking the stand they will not improve the API because Java 8 is the more logical upgrade path. Meanwhile, each call to the Guava libraries we add to our code will cost us time later on when we want to remove the dependency. So teams that are going to migrate to Java 8 (like us!) don’t have it in their best interest to keep using Guava.

What’s the alternative? We’re currently experimenting with using StreamSupport, the backported version of the Java 8 stream API to Java 6 and 7.

The rest of this post will explain our findings, and some strategies to help the migration. I will keep this post updated when I uncover new information. Of course, feel free to remark on things I have missed!

#Contents

#The theory, or “Why do we have to jump through all these hoops?”

##Java 8 vs Java 7 First of all, Java 8 brought a lot of improvements that you simply will have to miss in Java 7. This means that code will not be as concise under Java 7. In some cases, you might need completely different workarounds. Please note that I’m not aiming to list all Java 7/8 differences, only those relevant to the streams API.

###Lambdas The most obvious one is lambdas for functional interfaces:

//Java 8
List<String> strings = Arrays.asList("abc", "b", "de");
Collections.sort(strings, (a, b) -> (a.length() - b.length()));
System.out.println(strings);

ExecutorService pool = Executors.newCachedThreadPool();
pool.submit(() -> 42);
//Java 7
List<String> strings = Arrays.asList("abc", "b", "de");
Collections.sort(strings, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return (a.length() - b.length());
    }
});
System.out.println(strings);

ExecutorService pool = Executors.newCachedThreadPool();
pool.submit(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        return 42;
    }
});

Much of the boilerplate can be hidden by extracting the anonymous inner class implementation to another class, possibly with its own factory method, which has the bonus advantage of forcing you to name the otherwise anonymous piece of code.

//Java 7
List<String> strings = Arrays.asList("abc", "b", "de");
Collections.sort(strings, byLengthReversed());
System.out.println(strings);

ExecutorService pool = Executors.newCachedThreadPool();
pool.submit(new AnswerCalculator());
//Java 7 - other files
private static Comparator<String> byLengthReversed() {
    return new ReverseLengthComparator();
}

private static class ReverseLengthComparator implements Comparator<String> {
    @Override
    public int compare(String a, String b) {
        return (a.length() - b.length());
    }
}

private static class AnswerCalculator implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 42;
    }
}

###Method references This would take a long time to explain in depth, so I will just leave you with an example:

//Java 7
Supplier<List<?>> listFactory = new Supplier<List<?>>() {
    @Override
    public List<?> get() {
        return new ArrayList<>();
    }
};
//Java 8
Supplier<List<?>> listFactory = ArrayList::new;

Oracle provides an indepth overview.

###Generics Java 8 comes with improvements in type inference for generics related to generic parameters. Note that it seems like the proposed type inference on method chains did not make it. The example below demonstrates the change that was implemented.

Given the following class definition:

class Enhancer<T> {
    static <Z> Enhancer<Z> withSparklyEffects() {/*..*/ return new Enhancer<>();}
    static <Z> Enhancer<Z> addLayer(Z object, Enhancer<Z> effect) {/*..*/ return new Enhancer<>();}
    T result() {/*..*/return null;}
}

Sample usage looks like:

//Java 7
private String sparkles() {
    return addLayer("A pony", Enhancer.<String>withSparklyEffects()).result();
}
//Java 8
private String sparkles() {
    return addLayer("A pony", withSparklyEffects()).result();
}

Note that, because the generic type cannot be inferred in Java 7, the method call must be annotated with the type. The syntax for this makes it mandatory to add the class name in front of the method.

In Java 8 however the generic type can be inferred. This opens the option to statically import the method name, which can lead to more readable code, as seen above.

###Default interface methods Java 8 adds the ability to define default methods on interfaces.

public interface Die {
    int roll();
    default Die plus(int modifier) {
        return new Die() {
            @Override
            public int roll() {
                return Die.this.roll() + modifier;
            }
        };
    }
}
public class SixSidedDie implements Die {
    public static SixSidedDie d6() {
        return new SixSidedDie();
    }
    @Override
    public int roll() {
        return new Random().nextInt(6) + 1;
    }
}
public static void main(String... args) {
    Die modifiedD6 = d6().plus(2);
    System.out.printf("1d6+2 => %d\n", modifiedD6.roll());
}

##StreamSupport

###Package differences StreamSupport lives in its own package java8.util.stream, which resembles the Java 8 package java.util.stream. This makes it possible for the two to co-exist, but it makes removing the StreamSupport dependency a little bit tricky once you switch to Java 8. Often, you will be able to simply remove the 8 from the package name, but this won’t always work. Some examples of this are below.

###Starting a stream StreamSupport is not entirely able to recreate the Java 8 API, for one simple reason: you can’t change the existing Java 7 implementations of the Java standard library. In particular, and most crucially, the java.util.Collection.stream() method can’t be added to Java 7.

StreamSupport solves this by moving this method to a wrapper method:

//Java 8
private static long countWithStream(List<?> list) {
    return list.stream().count();
}
//StreamSupport
private static long countWithStream(List<?> list) {
    return StreamSupport.stream(list).count();
}

Note that this is the same approach as Guava:

//Guava
private static long countWithStream(List<?> list) {
    return FluentIterable.from(list).size();
}

If you want to remove the dependency on StreamSupport after introducing Java 8, you will have to change all these calls. We haven’t tried this yet, but I am pretty certain we will be able to solve this problem with IntelliJ’s structural search and replace.

###Predicate methods Java 8 defines Predicate composition as default methods on the Predicate interface: Predicate.and, Predicate.or, Predicate.negate.

//Java 8
private static Predicate<Integer> between5And10() {
    return greaterThan5().and(lessThan10());
}

private static Predicate<Integer> greaterThan5() {
    return integer -> integer > 5;
}

private static Predicate<Integer> lessThan10() {
    return integer -> integer < 10;
}

Streamsupport moves these to a java8.util.functions.Predicates class, and makes them wrapper methods.

//StreamSupport
private static Predicate<Integer> between5And10() {
    return Predicates.and(greaterThan5(), lessThan10());
}

private static Predicate<Integer> greaterThan5() {
    return new Predicate<Integer>() {
        @Override
        public boolean test(Integer integer) {
            return integer > 5;
        }
    };
}

private static Predicate<Integer> lessThan10() {
    return new Predicate<Integer>() {
        @Override
        public boolean test(Integer integer) {
            return integer < 10;
        }
    };
}

Probably another job for structural search and replace.

##Wrappers The Guava concepts of Function and Predicate are, of course, the same concepts as those in Java 8, and thus StreamSupport. While it is preferable to refactor an existing Function/Predicate to the new interface, sometimes there are a few too many usages to immediately change all depending code. It should however be no surprise we can translate from one implementation to the other. We use the following wrappers:

import java8.util.function.Function;
import java8.util.function.Predicate;

public class Guava2Java {

    private Guava2Java() { }

    public static <T> Predicate<T> j(final com.google.common.base.Predicate<T> guavaPredicate) {
        return new Predicate<T>() {
            @Override
            public boolean test(T t) {
                return guavaPredicate.apply(t);
            }
        };
    }

    public static <T, R> Function<T, R> j(final com.google.common.base.Function<T, R> guavaFunction) {
        return new Function<T, R>() {
            @Override
            public R apply(T t) {
                return guavaFunction.apply(t);
            }
        };
    }
}

We choose the short name to be minimally intruding:

private static List<String> makeAList() {
    return StreamSupport.stream(Arrays.asList("abc", "def", "bubble"))
            .map(j(guavaVersionOfToUppercase()))
            .collect(Collectors.<String>toList());
}

private static com.google.common.base.Function<String, String> guavaVersionOfToUppercase() {
    return new com.google.common.base.Function<String, String>() {
        @Override
        public String apply(String s) {
            return s.toUpperCase();
        }
    };
}

#The practice, or “How do I…”

##Collect to a list

//Guava
private static List<Integer> createList() {
    return FluentIterable.from(Arrays.asList(4, 5, 6))
            .filter(greaterThan5())
            .toList();
}
//StreamSupport
private static List<Integer> createList() {
    return StreamSupport.stream(Arrays.asList(4, 5, 6))
            .filter(greaterThan5())
            .collect(Collectors.<Integer>toList());
}
//Java 8
private static List<Integer> createList() {
    return Arrays.asList(4, 5, 6).stream()
            .filter(greaterThan5())
            .collect(Collectors.toList());
}

Note that you need the generic type in the StreamSupport version.

##Collect to SortedSet

//Guava
private static SortedSet<Integer> createSet() {
    return FluentIterable.from(Arrays.asList(4, 5, 6))
            .filter(greaterThan5())
            .toSortedSet(Ordering.natural());
}
//StreamSupport
private static SortedSet<Integer> createSet() {
    return StreamSupport.stream(Arrays.asList(4, 5, 6))
            .filter(greaterThan5())
            .collect(AdditionalCollectors.<Integer>toSortedSet());
}
//StreamSupport ctd - other file
public static class AdditionalCollectors {
    private static <T> Supplier<SortedSet<T>> sortedSetSupplier() {

        return new Supplier<SortedSet<T>>() {
            @Override
            public SortedSet<T> get() {
                return new TreeSet<>();
            }
        };
    }

    public static <T> Collector<? super T, ?, SortedSet<T>> toSortedSet() {
        return Collectors.toCollection(AdditionalCollectors.<T>sortedSetSupplier());
    }
}
//Java 8
private static SortedSet<Integer> createSet() {
    return Arrays.asList(4, 5, 6).stream()
            .filter(greaterThan5())
            .collect(Collectors.toCollection(TreeSet::new));
}

Generalizing these solutions to also accept a comparator is an excellent exercise for the reader.

##Combine predicates

//Guava
public Predicate<Integer> isOddNatural() {
    return and(isPositive(), not(isEven()));
}
//StreamSupport
public Predicate<Integer> isOddNatural() {
    return and(isPositive(), negate(isEven()));
}
//Java 8
public Predicate<Integer> isOddNatural() {
    return isPositive().and(isEven().negate());
}

#Conclusion Functional programming in Java 7 is not great, but it is workable. Upgrading to Java 8 should not be a problem, but removing all dependencies and cleaning all the boilerplate code might prove to be a hard job. StreamSupport can help, but you can still expect to have some work.