Minborg

Minborg
Minborg

Friday, April 3, 2015

Return Constructed Values in Java 8

Background

Very often when you are writing a method, you create an object, work with it a bit and then return the object or something that depends on the newly created object. In Java 8, you can do this in one step.

This (short) post shows a simple, yet very convenient and power-full, way of writing small methods i Java 8 where you do not have to write so much boiler plate code as you used to do in previous Java versions.

The pre-Java 8 Way

Suppose you want to create a method that is using a StringBuilder, add some stuff to the builder and then return a String using the StringBuilder's toString() method. If you are using a pre-Java 8 version you would most likely do it like this:

    public String poem() {
        final StringBuilder sb = new StringBuilder();
        sb.append("Hey there my dearest baby small, ");
        sb.append("soon you will be big and tall");
        return sb.toString();
    }
and if you would like to build an unmodifiable Set of Strings you could do something like this:

    public Set<String> suits() {
        final Set<String> result = new HashSet<>();
        result.add("Hearts");
        result.add("Spades");
        result.add("Diamonds");
        result.add("Clubs");
        return Collections.unmodifiableSet(result);
    }
As can be seen above, we first create something (i.e. a StringBuilder or a Set<String>), then we mutate that object (i.e. sb.append() or result.add()) and then finally we are finishing things up by applying some kind of function on the thing we created in the first place.

One Java 8 Way

By writing a small utility method like this...

    public <T, R> R with(Supplier<T> supplier, Consumer<T> mutator, Function<T, R> finisher) {
        final T start = supplier.get();
        mutator.accept(start);
        return finisher.apply(start);
    }

... we could make our methods a bit smaller. The method above takes two generic types, namely T which is the initial type we are working with (e.g. a StringBuilder) and R which is the return type (e.g. String). First the supplier is called so that we can obtain the initial start object (e.g. an instance of a StringBuilder). Then the mutator is called on the initial start object (e.g. we add stings to the StringBuilder). Then, lastly, the finisher is applied so that the right return type may be obtained (e.g. the initial StringBuilder instance is converted to a String). We could now start writing the previous examples in another way:

    public String poem() {
        return with(StringBuilder::new,
                sb -> sb
                .append("Hey there my dearest baby small, ")
                .append("soon you will be big and tall"),
                StringBuilder::toString);
    }
The first (Supplier) parameter in the call of the with() method is StringBuilder::new which is a method reference to StringBuilder's parameter-less constructor. The second (Consumer) argument will receive the newly created instance of the StringBuilder and it will then mutate it (i.e. call add() to add stuff to it). Finally, the third (Function) argument will take the newly created mutated instance and modify it to something else. In this case we will (again) us a method reference to obtain the object we are returning. In this case StringBuilder::toString will convert the mutated newly created object and map it to a String using the parameter-less function toString().

Here is the other example that builds an unmodifiable Set:

    public Set<String> suits() {
        return with(HashSet<String>::new, s -> {
            s.add("Hearts");
            s.add("Spades");
            s.add("Diamonds");
            s.add("Clubs");
        }, Collections::unmodifiableSet);
    }
This is sometimes refereed to as "Dependency Inversion". If this is an improvement over the pre-Java 8 ways is up to the reader to decide. I think one advantage is that you focus more on what to do rather than how to do it. On the other hand it is not so much less code as one might have hoped for and it is less clear what is going on for some one else not familiar with the concept.

If you do not want to run the finisher (perhaps you are satisfied with just a Set and does not need an unmodifiable Set), you could always skip the last step as shown below:

    public Set<String> suitsMutable() {

        return with(HashSet<String>::new, s -> {
            s.add("Hearts");
            s.add("Spades");
            s.add("Diamonds");
            s.add("Clubs");
        }, Function.identity());
    }
The static Function.identity() Function is (as its name implies) an identity function, meaning that it will always return exactly the same instance that you are providing to the function.


Using Java 8 Streams

Another way of accomplishing the same thing without using any utility method would be to use Java 8's built in Stream functions. The methods would then look something like this:

    public String poem() {
        return Stream.of("Hey there my dearest baby small, ", "soon you will be big and tall")
                .collect(joining());
    }

    public Set<String> suits() {
        return Collections.unmodifiableSet(
                Stream.of("Hearts", "Spades", "Diamonds", "Clubs")
                .collect(toSet()));
    }
Note that I have imported Collectors.joining and Collectors.toSet statically (this makes the code a bit smaller).

This is certainly an improvement over the previous methods. Less code and more in line with Java 8 standard coding pattern. However, sometimes you do not have the required collectors or finishers if you work with custom return objects. In some of those cases you can use the Collectors.collectingAndThen() method which allows you to run a normal collector and then apply a finisher on it. In the suits method example, it could look like this:

    public Set<String> suits() {
        return Stream.of("Hearts", "Spades", "Diamonds", "Clubs")
                .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));
    }
Again, I have imported the collectingAndThen() method statically.


Convenience Methods

If you create a convenience methods like this:

   public <T> Set<T> immutableSetOf(T... members) {
        return Stream.of(members)
                .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));
    }
then your can obtain your immutable Set very easily like this:

   public Set<String> suitsSimple() {
        return immutableSetOf("Hearts", "Spades", "Diamonds", "Clubs");
    }

Conclusions

Consider using Streams in your methods if you want to write simple and compact methods. Know your collectors and you will be able to save code space and time. A support method like the with() method can be useful in some circumstances.




No comments:

Post a Comment