Sunday, April 2, 2017

Java 8 – Lambda expressions, Streams and Collections

Table of contents

#1. History and motivation
#2. What is a Lambda expression?
#3. Lambdas and functional interfaces
#4. Moving from external to internal iteration
#5. Streams and Collections
#6. Streams and Parallelism
#7. Stream Interface use
#8. Recommended lectures



#1. History and motivation

Project Lambda was created in Nov 2009 and by Mach 2014 all its work saw the light when Java 8 was officially released. The changes introduced in the version were the biggest since the creation of the language, and among all the released features the lambda expression was the the most important.  Its introduction was not intended to be only a feature for the contentment of a group, but rather motivated by its benefits and the influence in the way of thinking about programming.

Lambda expressions empower Java with the functional programming paradigm. This might sound like something we could use rarely in some extraneous or advanced scenarios, but in reality it is complementary to the object-oriented programming paradigm already present in the Java language. In fact, functional programming usually is a more concise and expressive mechanism to model the behavior and complexity of a program. As hardware parallelism continues to increase to keep up with Moore’s law, the building blocks of functional programming immutable values and pure functions become effective tools for managing complexity and so make a better use of multi-core processors.



#2. What is a lambda expression?

A lambda expression (informally, “closure” or “anonymous method”) is a function with input values that produces an output value. In conventional Java terms lambdas can be understood as a kind of anonymous method with a compact syntax that allows the omission of modifiers, return type, and in some cases parameter types as well.

The general syntax of a lambda expression consists of an argument list (parameters), the arrow token ->, and a body. The body can be either a single expression or a statement block. In the expression form, the body is simply evaluated and returned. In the block form, the body is evaluated like a method body.


(parameters) -> expression

or


(parameters) -> { statements; }

Examples:
(String s) -> System.out.println(s)
FileFilter java = (File f) -> f.getName().endsWith(".java");
Comparator c = (s1, s2) -> s1.compareToIgnoreCase(s2);
Collections.sort(people, comparing(p -> p.getName()));

 

 

#3. Lambdas and functional interfaces

Functional interfaces (previously called SAM Types, which stood for “Single Abstract Method”) are interfaces that only have one abstract method. These interfaces are important as they can be implemented as anonymous classes with one method, representing a design that fits the format of a lambda expression. In fact, lambdas can be understood as a kind of anonymous method with a more compact syntax that allows the omission of modifiers, return type, throws clause, and in some cases parameter types as well.

Lambda expressions allow us to define an anonymous method and treat it as an instance of a functional interface. Using this approach to model Lambda expressions in the Java language is significant because it fits very cleanly into the type system and also it is convenient for a number of other reasons: interfaces are already an intrinsic part of the language, they naturally have a runtime representation and they carry with them informal contracts expressed by Javadoc comments. Additionally, since existing libraries use functional interfaces extensively the approach also enables the use of lambda expressions with these libraries.

To illustrate, these are some of the functional interfaces present in Java 7 that can be used with lambda expressions:


java.lang.Runnable
java.util.concurrent.Callable
java.util.Comparator
java.io.FileFilter
java.beans.PropertyChangeListener

In addition, Java 8 adds the new package “java.util.function” containing some of the following functional interfaces which are expected to be commonly used:



Predicate -- a boolean-valued property of an object
Supplier -- provide an instance of a T (such as a factory)
Consumer -- an action to be performed on an object
Function<T,R> -- a function transforming a T to a R
UnaryOperator -- a function from T to T
BinaryOperator -- a function from (T, T) to T

Here is an example of how a lambda expression can be used with the functional interface “Runnable” already present in Java 7:

Using the traditional syntax


new Thread(new Runnable() {
   @Override
   public void run() {
      System.out.println("Hello world");
   }
}).start();

Using a lambda expression


new Thread(() -> {
   System.out.println("Hello world");
}).start();

Nothing special needs to be done to declare an interface as functional, the compiler identifies it based on its structure. However, the design intent that an interface be functional can be expressed by using the @FunctionalInterface annotation, in which case the compiler will validate that the interface meets the structural requirements.



#4. Moving from external to internal iteration

Prior to Java 8 the iteration over a collection was only possible by a concept or technique named external iteration. This concept, in which relies the Java collection framework, consists on a client implementing the Iterable interface in order to step sequentially through the elements of a collection. For example, for printing on console a list of strings we could write:


List list = Arrays.asList("One", "Two", "Three");

Prior to Java 5:


for (Iterator it = list.iterator(); it.hasNext(); ) {
   System.out.println((String) it.next());
}

Or in a clearer idiom, using the for-each loop introduced in Java 5:


for (String s: list) {
   System.out.println(s);
}

External iteration was a reasonable approach when the collection framework was designed. However, the sequential mechanism provided by the Iterable interface -including the for-each loop- is restrictive and limited to a single thread execution unit. With the new control flow techniques and multi-processor hardware availability external iteration gets inadequate or “obsolete”, so the concept of Internal iteration was introduced in Java 8 to upgrade these aspects of the language. The idea is that instead of controlling the iteration externally, the client delegates the iteration to the library using a lambda expression. As a result of this change, the equivalent code of previous examples is transformed to the following single line of code:

Using a lambda expression


list.forEach(s -> System.out.println(s));

Or using a method reference


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

Although internal iteration may look a small change, the difference is significant. Moving the control of the iteration from the client to the library allows not only a clear and a short syntax, but also introduces abstraction over the control flow operations. This enables the potentially use of laziness, parallelism, and out-of-order execution to improve the overall performance.



#5. Streams and Collections

The Streams library was introduced in Java 8 as a solution to enhance the manipulation over Collections and other data sources without losing focus in a better code and an easier parallelism. Defined in package “java.util.stream“, the new library introduces a Stream interface to model a sequence of values and operations. The interface allows common manipulations over its values by taking full advantage of both lambda expressions and the internal iteration concept. The library also provides convenient ways to obtain stream views of collections, arrays, and other data sources.

Collections and Streams, while having some similarities, are completely independent and have different goals. Streams do not store values as Collections do, their purpose is to process them. When all the collection values have been processed and provided by the stream, the stream is exhausted and cannot be used any further.

To illustrate, in this example the “forEach” method of list can be invoked several times.


List list = Arrays.asList("One", "Two", "Three");
list.forEach(System.out::println);

Using a stream, the second invoke to “forEach” will rise an Exception


Stream stream = list.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println);   -> Exception



Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
 at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:274)
 at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
 at main.Main.main(Main.java:20)

The central idea behind streams is Lazy evaluation, in which no value is ever computed until it is required. Iterators have this characteristic, however they differed from stream because their values are returned in an ordered sequence while in the streams the returned order might be unpredictable.

For example,  we can define a stream with words starting with “T”


List list = Arrays.asList("One", "Two", "Three");
Stream stream = list.stream().filter(s -> s.startsWith("T"));

However, no computation on the data will be performed unless a terminal operation like “count()” is invoked and source elements are consumed as needed.
System.out.println(stream.count());

The power of the Stream API lies in the possibilities that are created by composing its operations together. Using the API in combination with Lambda expressions and method references the produced code is much better: expressive, clean, maintainable, and easier to parallelize.

If you take a look at the Stream interface you can notice it was designed with powerful methods. This is important as these methods not only augment the set of operations that can be performed over the collections, but also liberates us from writing repetitive code by delegating that job to the API.



#6. Streams and Parallelism

Parallel processing has become so important that has changed some programming methods towards one that is agnostic about it is executed sequentially or in parallel, something that has been required to make a better use of hardware parallelism and to be able to manage the complexity of parallel-ready code. All operations of the Stream API encourages this agnosticism and are designed to have equivalent effect in sequential and in parallel modes.

However, this does not mean all operations will always produce the same result in each mode. Operations with nondeterministic behavior like “forEach” it might preserve ordering in sequential mode, however it’s a mistake to depend on this. It’s important to keep in mind that nondeterministic operations are equally nondeterministic in sequential and in parallel mode.

For example: To process a stream from a list, in the same order as elements are in the list, one might be tempted to write:


List list = Arrays.asList("One", "Two", "Three");
list.stream().forEach(...)

But in reality, to make sure that all the elements are processed in order the right approach would be:


list.stream().forEachOrdered(...)

On the contrary, if the processing order is not important we could write either one of the following two options:


1) list.stream().forEach(...)
2) list.parallelStream().forEach(...)

Notice the second option declaring an explicit parallel stream might have a best performance depending each case. However, not always working with parallel streams is the best option. It is programmer’s job to determine which approach is the best, but a sane rule could be to check that selected solutions are independent about their sequential or parallel execution.



#7. Stream Interface use

As a briefly introduction to the Stream interface below are some basic uses of its methods and how they work.


import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;

import org.junit.Test;

public class StreamTests {
    private static final List<String> THE_SORT_SLIST = Arrays.asList("One", "Two", "Three", "Four", "Five");
    private static final List<Integer> THE_SORT_LIST = Arrays.asList(1, 2, 3, 4, 5);

    /**
     * allMatch: Returns whether all elements of a stream match the provided
     * predicate.
     *
     * Signature: boolean allMatch(Predicate<? super T> predicate)
     */
    @Test
    public void testAllMatch() {
        // Using a lambda expression
        boolean result = THE_SORT_SLIST.stream().allMatch(s -> s.isEmpty());
        assertFalse(result);
        // Using a method reference
        result = THE_SORT_SLIST.stream().allMatch(String::isEmpty);
        assertFalse(result);
    }

    /**
     * anyMatch: Returns whether any elements of a stream match the provided
     * predicate.
     *
     * Signature: boolean anyMatch(Predicate<? super T> predicate)
     */
    @Test
    public void testAnyMatch() {
        // Using a lambda expression
        boolean result = THE_SORT_SLIST.stream().anyMatch(s -> s.isEmpty());
        assertFalse(result);
        // Using a method reference
        result = THE_SORT_SLIST.stream().anyMatch(String::isEmpty);
        assertFalse(result);
    }

    /**
     * Builder: Returns a builder for a Stream.
     *
     * Signature: static <T> Stream.Builder<T> builder()
     */
    @Test
    public void testBuilder() {
        Stream<String> stream = Stream.<String>builder().add("One").add("Two").add("Three").build();
        assertTrue(stream.count() > 0);
    }

    /**
     * Collect (1): Performs a mutable reduction operation on the elements of a
     * stream using a Collector.
     *
     * Signature: <R,A> R collect(Collector<? super T,A,R> collector)
     */
    @Test
    public void testCollect() {
        Set<String> set = THE_SORT_SLIST.stream().collect(Collectors.toSet());
        assertEquals(THE_SORT_SLIST.size(), set.size());
    }

    /**
     * Collect (2): Performs a mutable reduction operation on the elements of a
     * stream.
     *
     * Signature: <R> R collect(Supplier<R> supplier, BiConsumer<R,? super T>
     * accumulator, BiConsumer<R,R> combiner)
     */
    @Test
    public void testCollectSupplier() {
        Set<String> set = THE_SORT_SLIST.stream().collect(HashSet::new, Set::add, Set::addAll);
        assertEquals(THE_SORT_SLIST.size(), set.size());
    }

    /**
     * Concat: Creates a lazily concatenated stream whose elements are all the
     * elements of the first stream followed by all the elements of the second
     * stream.
     *
     * Concat: static <T> Stream<T> concat(Stream<? extends T> a, Stream<?
     * extends T> b)
     */
    @Test
    public void testConcat() {
        Stream<String> s1 = Stream.of("One", "Three", "Five", "Seven", "Nine");
        Stream<String> s2 = Stream.of("Two", "Four", "Six", "Eight", "Ten");
        String concat = Stream.concat(s1, s2).collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
                .toString();
        assertEquals("OneThreeFiveSevenNineTwoFourSixEightTen", concat);
    }

    /**
     * Distinct: Returns a stream consisting of the distinct elements (according
     * to Object.equals(Object)) of a stream.
     *
     * Signature: Stream<T> distinct()
     */
    @Test
    public void testDistinct() {
        Stream<Integer> si = Stream.<Integer>builder().add(1).add(1).add(1).add(1).add(2).add(2).add(3).add(4).add(5)
                .add(5).build();
        Stream<Integer> sd = si.distinct();
        assertEquals(5, sd.count()); // 1, 2, 3, 4, 5
    }

    /*
     * Filter: Returns a stream consisting of the elements of a stream that
     * match the given predicate.
     *
     * Signature: Stream<T> filter(Predicate<? super T> predicate)
     */
    @Test
    public void testFilter() {
        Set<String> set = THE_SORT_SLIST.stream().filter(s -> s.startsWith("T")).collect(Collectors.toSet());
        assertEquals(2, set.size()); // "Two" && "Three"
    }

    /*
     * FindAny: Returns an Optional describing some element of the stream, or an
     * empty Optional if the stream is empty.
     *
     * Signature: Optional<T> findAny()
     */
    @Test
    public void testFindAny() {
        Optional<String> opt = THE_SORT_SLIST.stream().findAny();
        assertTrue(opt.isPresent());
    }

    /*
     * findFirst: Returns an Optional describing the first element of this
     * stream, or an empty Optional if the stream is empty.
     *
     * Signature: Optional<T> findFirst()
     */
    @Test
    public void testFindFirst() {
        Optional<String> opt = THE_SORT_SLIST.stream().findFirst();
        assertTrue(opt.isPresent());
        assertEquals("One", opt.get());
    }

    /*
     * flatMap: Returns a stream consisting of the results of replacing each
     * element of a stream with the contents of a mapped stream produced by
     * applying the provided mapping function to each element.
     *
     * Signature: <R> Stream<R> flatMap(Function<? super T,? extends Stream<?
     * extends R>> mapper)
     */
    @Test
    public void testFlatMap() {
        List<List<String>> theTenList = new ArrayList<>();
        theTenList.add(Arrays.asList("One", "Three", "Five", "Seven", "Nine"));
        theTenList.add(Arrays.asList("Two", "Four", "Six", "Eight", "Ten"));
        Stream<String> ss = theTenList.stream().flatMap(list -> list.stream().filter(s -> s.startsWith("T")));
        assertEquals(3, ss.count()); // "Three", "Two" & "Ten"
    }

    /*
     * flatMapToDouble: Returns an DoubleStream consisting of the results of
     * replacing each element of a stream with the contents of a mapped stream
     * produced by applying the provided mapping function to each element.
     *
     * Signature: DoubleStream flatMapToDouble(Function<? super T,? extends
     * DoubleStream> mapper)
     */
    @Test
    public void testFlatMapToDouble() {
        List<List<String>> theTenList = new ArrayList<>();
        theTenList.add(Arrays.asList("One", "Three", "Five", "Seven", "Nine"));
        theTenList.add(Arrays.asList("Two", "Four", "Six", "Eight", "Ten"));
        DoubleStream ds = theTenList.stream().flatMapToDouble(list -> list.stream().mapToDouble(s -> s.length()));
        assertEquals(39d, ds.sum(), 0); // 39 = 3, 5, 4, 5, 4, 3, 4, 3, 5, 3
    }

    /*
     * flatMapToInt: Returns an IntStream consisting of the results of replacing
     * each element of a stream with the contents of a mapped stream produced by
     * applying the provided mapping function to each element.
     *
     * Signature: IntStream flatMapToInt(Function<? super T,? extends IntStream>
     * mapper)
     */
    @Test
    public void testFlatMapToInt() {
        List<List<Integer>> mlist = Arrays.asList(Arrays.asList(1, 2, 3, 4, 5), Arrays.asList(6, 7, 8, 9, 10));
        IntStream ds = mlist.stream().flatMapToInt(l -> l.stream().mapToInt(i -> i));
        assertEquals(55, ds.sum(), 0); // 55 = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9
                                        // + 10
    }

    /*
     * flatMapToLong: Returns an LongStream consisting of the results of
     * replacing each element of a stream with the contents of a mapped stream
     * produced by applying the provided mapping function to each element.
     *
     * Signature: LongStream flatMapToLong(Function<? super T,? extends
     * LongStream> mapper)
     */
    @Test
    public void testFlatMapToLong() {
        List<List<Long>> mlist = Arrays.asList(Arrays.asList(1l, 2l, 3l, 4l, 5l), Arrays.asList(6l, 7l, 8l, 9l, 10l));
        LongStream ds = mlist.stream().flatMapToLong(l -> l.stream().filter(n -> n % 2 == 0).mapToLong(i -> i));
        assertEquals(30l, ds.sum()); // 30 = 2 + 4 + 6 + 8 + 10
    }

    /**
     * ForEach: Performs an action for each element of a stream.
     *
     * Signature: void forEach(Consumer<? super T> action)
     */
    @Test
    public void testForEach() {
        StringBuilder sb = new StringBuilder();
        THE_SORT_SLIST.stream().forEach(s -> sb.append(s));
        assertEquals("OneTwoThreeFourFive", sb.toString());
    }

    /**
     * forEachOrdered: Performs an action for each element of a stream, in the
     * encounter order of the stream if the stream has a defined encounter
     * order.
     *
     * Signature: void forEachOrdered(Consumer<? super T> action)
     */
    @Test
    public void testForEachOrdered() {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            list.add(i);
        }
        // Append in order
        StringBuffer sbOrdered = new StringBuffer();
        list.parallelStream().forEachOrdered(s -> sbOrdered.append(s));
        // Append without order
        StringBuffer sbUnordered = new StringBuffer();
        list.parallelStream().forEach(s -> sbUnordered.append(s));
        // Strings should be different
        assertFalse(sbOrdered.toString().equals(sbUnordered.toString()));
    }

    /**
     * generate: Returns an infinite sequential unordered stream where each
     * element is generated by the provided Supplier.
     *
     * Signature: static <T> Stream<T> generate(Supplier<T> s)
     */
    @Test
    public void testGenerate() {
        List<Double> list = Stream.generate(() -> Math.random()).limit(10).collect(Collectors.toList());
        assertEquals(10, list.size());
    }

    /**
     * iterate: Returns an infinite sequential ordered stream produced by
     * iterative application of a function f to an initial element seed,
     * producing a stream consisting of seed, f(seed), f(f(seed)), etc.
     *
     * Signature: static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
     */
    @Test
    public void testIterate() {
        // To generate an ordered list from 0 to 9
        List<Integer> list = Stream.iterate(0, n -> n + 1).limit(10).collect(Collectors.toList());
        assertEquals("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]", list.toString());
        // To generate a list with alternated values of 0 and 1
        list = Stream.iterate(0, n -> n == 0 ? 1 : 0).limit(10).collect(Collectors.toList());
        assertEquals("[0, 1, 0, 1, 0, 1, 0, 1, 0, 1]", list.toString());
    }

    /**
     * limit: Returns a stream consisting of the elements of this stream,
     * truncated to be no longer than maxSize in length.
     *
     * Signature: Stream<T> limit(long maxSize)
     */
    @Test
    public void testLimit() {
        List<String> list = THE_SORT_SLIST.stream().limit(3).collect(Collectors.toList());
        assertEquals("One", list.get(0));
        assertEquals("Two", list.get(1));
        assertEquals("Three", list.get(2));
    }

    /**
     * Map: Returns a stream consisting of the results of applying a function to
     * the elements of a stream.
     *
     * Signature: <R> Stream<R> map(Function<? super T,? extends R> mapper)
     */
    @Test
    public void testMap() {
        // Using a lambda expression
        Set<String> set = THE_SORT_SLIST.stream().map(s -> s.toUpperCase()).collect(Collectors.toSet());
        assertEquals(THE_SORT_SLIST.size(), set.size());
        assertTrue(set.contains("THREE"));
        // Using a method reference
        set = THE_SORT_SLIST.stream().map(String::toUpperCase).collect(Collectors.toSet());
        assertEquals(THE_SORT_SLIST.size(), set.size());
        assertTrue(set.contains("FIVE"));
    }

    /**
     * Max: Returns the maximum element of a stream according to the provided
     * Comparator.
     *
     * Signature: Optional<T> max(Comparator<? super T> comparator)
     */
    @Test
    public void testMax() {
        Optional<String> op = Stream.of("Zero", "Eleven", "One").max(Comparator.comparing(String::length));
        assertEquals("Eleven", op.get());
    }

    /**
     * Min: Returns the minimum element of a stream according to the provided
     * Comparator.
     *
     * Signature: Optional<T> min(Comparator<? super T> comparator)
     */
    @Test
    public void testMin() {
        Optional<String> op = Stream.of("Zero", "Eleven", "One").min(Comparator.comparing(String::length));
        assertEquals("One", op.get());
    }

    /**
     * NoneMatch: Returns whether no elements of this stream match the provided
     * predicate.
     *
     * Signature: boolean noneMatch(Predicate<? super T> predicate)
     */
    @Test
    public void testNoneMatch() {
        boolean result = THE_SORT_SLIST.stream().noneMatch(s -> s.equals("Zero"));
        assertTrue(result);
    }

    /**
     * Of: Returns a sequential ordered stream whose elements are the specified
     * values.
     *
     * Signature: static <T> Stream<T> of(T... values)
     */
    @Test
    public void testOf() {
        Stream<String> ss = Stream.of("Zero", "Eleven", "Twelve");
        assertEquals(3, ss.count());
    }

    /**
     * Peek: Returns a stream consisting of the elements of a stream,
     * additionally performing the provided action on each element as elements
     * are consumed from the resulting stream. This method was provided to
     * support debugging and, because it works by means of side-effects, should
     * not be used for any other purpose.
     *
     * Signature: Stream<T> peek(Consumer<? super T> action)
     */
    @Test
    public void testPeek() {
        StringBuilder sbLowercase = new StringBuilder();
        StringBuilder sbUppercase = new StringBuilder();
        long count = THE_SORT_SLIST.stream().map(String::toLowerCase).peek(s -> sbLowercase.append(s))
                .map(String::toUpperCase).peek(s -> sbUppercase.append(s)).count();
        assertEquals(5, count);
        assertEquals("onetwothreefourfive", sbLowercase.toString());
        assertEquals("ONETWOTHREEFOURFIVE", sbUppercase.toString());
    }

    /**
     * Reduce (1): Performs a reduction on the elements of a stream, using an
     * associative accumulation function, and returns an Optional describing the
     * reduced value, if any.
     *
     * Signature: Optional<T> reduce(BinaryOperator<T> accumulator)
     */
    @Test
    public void testReduceAccumulator() {
        // Using sequential stream
        Optional<String> op = THE_SORT_SLIST.stream().reduce((s1, s2) -> {
            return s1 + "," + s2;
        });
        assertEquals("One,Two,Three,Four,Five", op.get());
        // Using parallel stream
        op = THE_SORT_SLIST.parallelStream().reduce((s1, s2) -> {
            return s1 + "," + s2;
        });
        assertEquals("One,Two,Three,Four,Five", op.get());
    }

    /**
     * Reduce (2): Performs a reduction on the elements of this stream, using
     * the provided identity value and an associative accumulation function, and
     * returns the reduced value.
     *
     * Signature: T reduce(T identity, BinaryOperator<T> accumulator)
     */
    @Test
    public void testReduceIdentityAccumulator() {
        // NOTE: Below examples are dependent about their sequential or parallel
        // execution. Examples are coded with the intention to show an scenario
        // might happend, however this behaviour is something we should try to
        // avoid.
        //
        // 1) Using sequential stream
        int total = THE_SORT_LIST.stream().reduce(10, (n1, n2) -> {
            return n1 + n2;
        });
        assertEquals(10 + 1 + 2 + 3 + 4 + 5, total);
        // 2) Using parallel stream
        total = THE_SORT_LIST.parallelStream().reduce(10, (n1, n2) -> {
            return n1 + n2;
        });
        assertEquals(11 + 12 + 13 + 14 + 15, total);
    }

    /**
     * Reduce (3): Performs a reduction on the elements of this stream, using
     * the provided identity, accumulation and combining functions.
     *
     * Signature: <U> U<T> reduce(U identity, BiFunction<U,? super T,U>
     * accumulator, BinaryOperator<U> combiner)
     */
    @Test
    public void testReduceIdentityAccumulatorCombiner() {
        // NOTE: Below examples are dependent about their sequential or parallel
        // execution. Examples are coded with the intention to show an scenario
        // might happend, however this behaviour is something we should try to
        // avoid.
        //
        // 1) Using sequential stream (Identity = 10)
        int total = THE_SORT_LIST.stream().reduce(10, (n1, n2) -> {
            return n1 + n2;
        }, (n1, n2) -> {
            return n1 * n2;
        });
        assertEquals(10 + 1 + 2 + 3 + 4 + 5, total);
        // 2) Using parallel stream (Identity = 10)
        total = THE_SORT_LIST.parallelStream().reduce(10, (n1, n2) -> {
            return n1 + n2;
        }, (n1, n2) -> {
            return n1 * n2;
        });
        assertEquals((10 + 1) * (10 + 2) * (10 + 3) * (10 + 4) * (10 + 5), total);
    }

    /**
     * skip: Returns a stream consisting of the remaining elements of this
     * stream after discarding the first n elements of the stream.
     *
     * Signature: Stream<T> skip(long n)
     */
    @Test
    public void testSkip() {
        List<String> list = THE_SORT_SLIST.stream().skip(3).collect(Collectors.toList());
        assertEquals("Four", list.get(0));
        assertEquals("Five", list.get(1));
    }

    /**
     * sorted (1): Returns a stream consisting of the elements of this stream,
     * sorted according to natural order.
     *
     * Signature: Stream<T> sorted()
     */
    @Test
    public void testSorted() {
        List<String> list = THE_SORT_SLIST.stream().sorted().collect(Collectors.toList());
        assertEquals("Five", list.get(0));
        assertEquals("Four", list.get(1));
        assertEquals("One", list.get(2));
        assertEquals("Three", list.get(3));
        assertEquals("Two", list.get(4));
    }

    /**
     * sorted (2): Returns a stream consisting of the elements of this stream,
     * sorted according to the provided Comparator.
     *
     * Signature: Stream<T> sorted(Comparator<? super T> comparator)
     */
    @Test
    public void testSortedComparator() {
        List<String> unsortedList = Arrays.asList("ccczz", "eeeee", "bbzzz", "ddddz", "azzzz");
        // Using an anonymous comparator
        List<String> sortedList = unsortedList.stream().sorted((e1, e2) -> {
            return e1.compareTo(e2);
        }).collect(Collectors.toList());
        assertEquals("azzzz", sortedList.get(0));
        assertEquals("bbzzz", sortedList.get(1));
        assertEquals("ccczz", sortedList.get(2));
        assertEquals("ddddz", sortedList.get(3));
        assertEquals("eeeee", sortedList.get(4));
        // Using Comparator.comparing
        sortedList = unsortedList.stream().sorted(Comparator.comparing(String::toString)).collect(Collectors.toList());
        assertEquals("azzzz", sortedList.get(0));
        assertEquals("bbzzz", sortedList.get(1));
        assertEquals("ccczz", sortedList.get(2));
        assertEquals("ddddz", sortedList.get(3));
        assertEquals("eeeee", sortedList.get(4));
    }

}

Code can be downloaded from GitHub following the link: https://github.com/barrenaedu/tutorials.java8.streams



#8. Recommended lectures

No comments:

Post a Comment