Sunday, August 6, 2017

Java 8 - Streams

A stream is a sequence of elements from a source that supports data processing operations.

Streams make use of internal iteration: the iteration is abstracted away through operations such as filter, map, and sorted.

There are two types of stream operations: intermediate and terminal operations.
Intermediate operations such as filter and map return a stream and can be chained together. They’re used to set up a pipeline of operations but don’t produce any result.
Terminal operations such as forEach and count return a nonstream value and process a stream pipeline to return a result.

The elements of a stream are computed on demand.

You can filter and slice a stream using the filter, distinct, skip, and limit methods.
You can extract or transform elements of a stream using the map and flatMap methods.
You can find elements in a stream using the findFirst and findAny methods. You can match a given predicate in a stream using the allMatch, noneMatch, and anyMatch methods.
These methods make use of short-circuiting: a computation stops as soon as a result is found; there’s no need to process the whole stream.
You can combine all elements of a stream iteratively to produce a result using the reduce method, for example, to calculate the sum or find the maximum of a stream.
Some operations such as filter and map are stateless; they don’t store any state. Some operations such as reduce store state to calculate a value. Some operations such as sorted and distinct also store state because they need to buffer all the elements of a stream before returning a new stream. Such operations are called stateful operations.
There are three primitive specializations of streams: IntStream, DoubleStream, and LongStream. Their operations are also specialized accordingly.

Examples


      /** get dish names that have less than 400 calories sorted by calories */
    public static List<String> getLowCaloricDishesNames(List<Dish> dishes) {
        return dishes.stream()
                .filter(d -> d.getCalories() < 400)
                .sorted(Comparator.comparing(Dish::getCalories))
                .map(Dish::getName)
                .collect(Collectors.toList());
    }


    //* finds the first square that’s divisible by 3 */    
    List<Integer> numbers= Arrays.asList(1, 2, 3, 4, 5); 
    Optional<Integer> firstSquareDivisibleByThree = numbers.stream()
             .map(x -> x * x) 
             .filter(x -> x % 3 == 0) 
             .findFirst(); // 9
    
    /** sum the elements of a list of numbers */
    List<Integer> numbers = Arrays.asList(3,4,5,1,2);
    int sum = numbers.stream().reduce(0, Integer::sum);

The problem with this code is that there’s an insidious boxing cost. Behind the scenes each Integer needs to be unboxed to a primitive before performing the summation - better to call sum method on stream:
    int sum = numbers.stream().sum();

    // find maximum element using reduce function
    int max = numbers.stream().reduce(0, (a, b) -> Integer.max(a, b));

    // when there is no initial value, return value is Optional
    Optional<Integer> min = numbers.stream().reduce(Integer::min);
    min.ifPresent(System.out::println);

    // count using map-reduce pattern
    int count = numbers.stream().map(d -> 1).reduce(0, (a, b) -> a + b);

Common data processing idiom is finding whether some elements in a set of data match a given property. The Streams API provides such facilities through the allMatch, anyMatch, noneMatch, findFirst, and findAny methods of a stream.

The anyMatch method can be used to answer the question “Is there an element in the stream matching the given predicate?”

    List<Integer> numbers = Arrays.asList(3,4,5,1,2);
    boolean anyMatch = numbers.stream().anyMatch(n -> n == 5);



Creating streams

Streams can be created not only from a collection but also from values, arrays, files, and specific methods such as iterate and generate.

        // Stream.of
        Stream<String> stream = Stream.of("Java 8", "Lambdas", "In", "Action");
        stream.map(String::toUpperCase).forEach(System.out::println);

        // Stream.empty
        Stream<String> emptyStream = Stream.empty();

        // Arrays.stream
        int[] numbers = {2, 3, 5, 7, 11, 13};
        System.out.println(Arrays.stream(numbers).sum());

The Streams API provides two static methods to generate a stream from a function: Stream.iterate and Stream.generate. These two operations let you create what we call an infinite stream: a stream that doesn’t have a fixed size like when you create a stream from a fixed collection.
        // Stream.iterate
        Stream.iterate(0, n -> n + 2)
              .limit(10)
              .forEach(System.out::println);

        // stream of 1s with Stream.generate
        IntStream.generate(() -> 1)
                 .limit(5)
                 .forEach(System.out::println);

        // fibonnaci with iterate
        Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1],t[0] + t[1]})
              .limit(10)
              . map(t -> t[0])
              .forEach(System.out::println);

       // find out the number of unique words in a file
        Files.lines(Paths.get("/data.txt"), Charset.defaultCharset())
                                 .flatMap(line -> Arrays.stream(line.split(" ")))
                                 .distinct()
                                 .count();
You use flatMap to produce one flattened stream of words instead of multiple streams of words for each line.

Ref

Java 8 in Action book
http://www.baeldung.com/java-8-streams
http://www.mkyong.com/java8/java-8-streams-filter-examples/

No comments:

Post a Comment