Search

Dark theme | Light theme
Showing posts with label JavaJoy:Streams. Show all posts
Showing posts with label JavaJoy:Streams. Show all posts

July 22, 2023

Java Joy: Using mapMulti Method Of The Stream API

Since Java 16 we can use the method mapMulti(BiConsumer) of the Stream API. This method allows us to map each element of the stream to multiple elements. We can also do that with the flatMap(Function) method, but if we want to map a limited set of elements, mapMulti is more convenient. Internally a shared stream is used and we don’t have the cost of creating a new stream for each element. Another use case is if the logic to map an element to multiple elements is complex and is hard to implement by returning a stream. Then mapMulti allows us to write that logic in a BiConsumer instead of a Function.

In the following code we use the mapMulti method in several examples:

package mrhaki.stream;

import javax.print.attribute.HashPrintServiceAttributeSet;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MapMulti {

    public static void main(String[] args) {
        // We want to return a stream of string values 
        // and the uppercase variant
        // if the original element has the letter o.
        assert Stream.of("Java", "Groovy", "Clojure")
                     .mapMulti((language, downstream) -> {
                         if (language.contains("o")) {
                             downstream.accept(language);
                             downstream.accept(language.toUpperCase());
                         }
                     })
                     .toList()
                     .equals(List.of("Groovy", "GROOVY", "Clojure", "CLOJURE"));

        // Same logic implemented with flatMap.
        assert Stream.of("Java", "Groovy", "Clojure")
                     .filter(language -> language.contains("o"))
                     .flatMap(language -> Stream.of(language, language.toUpperCase()))
                     .toList()
                     .equals(List.of("Groovy", "GROOVY", "Clojure", "CLOJURE"));


        // Helper record to store a name and set of language names.
        record Developer(String name, List<String> languages) {}

        // Number of sample developers that work with different languages.
        var hubert = new Developer("mrhaki", List.of("Java", "Groovy", "Clojure"));
        var java = new Developer("java", List.of("Java"));
        var clojure = new Developer("clojure", List.of("Clojure"));
        var groovy = new Developer("groovy", List.of("Groovy"));

        record Pair(String name, String language) {}

        // Let's find all developers that have Java in their
        // set of languages and return a new Pair
        // object with the name of the developer and a language.
        assert Stream.of(hubert, java, clojure, groovy)
                     // We can explicitly state the class that will be 
                     // in the downstream of the compiler cannot
                     // deduct it using a <...> syntax.
                     .<Pair>mapMulti((developer, downstream) -> {
                         var languages = developer.languages();
                         if (languages.contains("Java")) {
                             for (String language : developer.languages()) {
                                 downstream.accept(new Pair(developer.name(), language));
                             }
                         }
                     })
                     .toList()
                     .equals(List.of(new Pair("mrhaki", "Java"),
                                     new Pair("mrhaki", "Groovy"),
                                     new Pair("mrhaki", "Clojure"),
                                     new Pair("java", "Java")));

        // Same logic using filter and flatMap.
        assert Stream.of(hubert, java, clojure, groovy)
                     .filter(developer -> developer.languages().contains("Java"))
                     .flatMap(developer -> developer.languages()
                                                    .stream()
                                                    .map(language -> new Pair(developer.name(), language)))
                     .toList()
                     .equals(List.of(new Pair("mrhaki", "Java"),
                                     new Pair("mrhaki", "Groovy"),
                                     new Pair("mrhaki", "Clojure"),
                                     new Pair("java", "Java")));
        
        
        // We want to expand each number to itself and its square root value
        // and we muse mapMultiToInt here.
        var summaryStatistics = Stream.of(1, 2, 3)
                                      .mapMultiToInt((number, downstream) -> {
                                          downstream.accept(number);
                                          downstream.accept(number * number);
                                      })
                                      .summaryStatistics();

        assert summaryStatistics.getCount() == 6;
        assert summaryStatistics.getSum() == 20;
        assert summaryStatistics.getMin() == 1;
        assert summaryStatistics.getMax() == 9;
    }
}

Written with Java 20.

March 19, 2021

Java Joy: Getting Multiple Results From One Stream With Teeing Collector

If we want to get two types of information from a Stream of objects we can consume the Stream twice and collect the results. But that is not very efficient, especially when the stream has a lot of objects. Since Java 12 we can use the teeing method of the java.util.stream.Collectors class to get multiple results while consuming the stream of objects only once. The teeing method takes two collectors as argument each returning a separate result for the stream items. As third argument we must pass a function that will merge the results of the two collectors into a new object.

In the following code we have two example use cases that use the teeing method to get multiple results while consuming a Stream of objects only one time:

package mrhaki.stream;

import java.util.List;
import java.util.Locale;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class Teeing {
    public static void main(String[] args) {
        // Simple list of language names.
        List<String> languageNames = List.of("Java", "Clojure", "Kotlin", "Groovy", "Scala");

        // Predicate for String value that has the letter j or J.
        Predicate<String> containsJ = s -> s.toLowerCase(Locale.ROOT).contains("j");

        // Collector to apply two filters for one stream process and combine the result.
        Collector<String, ?, List<List<String>>> splitOnLetterJ =
                Collectors.teeing(
                        // Filter on language name with a j or J.
                        Collectors.filtering(containsJ, Collectors.toList()),
                        // Filter on language name withtout a j or J.
                        Collectors.filtering(containsJ.negate(), Collectors.toList()),
                        // Combine both results into a  new List with two items.
                        (withJ, withoutJ) -> List.of(withJ, withoutJ));

        List<List<String>> split = languageNames.stream().collect(splitOnLetterJ);

        assert List.of("Java", "Clojure").equals(split.get(0));
        assert List.of("Kotlin", "Groovy", "Scala").equals(split.get(1));


        // List of language records with a name and 
        // boolean to indicate the language is dynamic or not. 
        List<Language> languages =
                List.of(new Language("Clojure", true),
                        new Language("Java", false),
                        new Language("Groovy", true),
                        new Language("Scala", false),
                        new Language("Kotlin", false));

        // Filter for dynamic languages and transform to list.
        Collector<Language, ?, List<Language>> filteringDynamicLanguages =
                Collectors.filtering(Language::dynamic, Collectors.toUnmodifiableList());
        
        // Make a list with the language names in upper case.
        Collector<Language, ?, List<String>> upperCaseLanguageNames =
                Collectors.mapping(language -> language.name.toUpperCase(Locale.ROOT),
                                   Collectors.toUnmodifiableList());

        // Function to merge both list into a list with first item the result
        // of filteringDynamicLanguages and the second item the result
        // of upperCaseLanguageNames.
        final BiFunction<List<Language>, List<String>, List<List<?>>> mergeLists =
                (dynamicLanguages, upperCaseLanguages) -> List.of(dynamicLanguages, upperCaseLanguages);

        List<List<?>> result = languages
                .stream()
                .collect(
                        Collectors.teeing(
                                filteringDynamicLanguages,
                                upperCaseLanguageNames,
                                mergeLists));

        assert List.of(new Language("Clojure", true), new Language("Groovy", true)).equals(result.get(0));
        assert List.of("CLOJURE", "JAVA", "GROOVY", "SCALA", "KOTLIN").equals(result.get(1));
    }

    // Record to store language name and if the language is dynamic.
    record Language(String name, boolean dynamic) {}
}

Written with Java 16.

February 25, 2021

Java Joy: Merge Maps Using Stream API

In Java we can merge a key/value pair into a Map with the merge method. The first parameter is the key, the second the value and the third parameter of the merge method is a remapping function that is applied when the key is already present in the Map instance. The remapping function has the value of the key in the original Map and the new value. We can define in the function what the resulting value should be. If we return null the key is ignored.

If we want to merge multiple Map instances we can use the Stream API. We want to convert the Map instances to a stream of Map.Entry instances which we then turn into a new Map instance with the toMap method from the class Collectors. The toMap method also takes a remapping function when there is a duplicate key. The function defines what the new value is based on the two values of the duplicate key that was encountered. We can choose to simply ignore one of the values and return the other value. But we can also do some computations in this function, for example creating a new value using both values.

In the following example we use the Stream API to merge multiple Map instances into a new Map using a remapping function for duplicate keys:

package com.mrhaki.sample;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MapMerge {
    public static void main(String[] args) {
        Map<Character, Integer> first = Map.of('a', 2, 'b', 3, 'c', 4);
        Map<Character, Integer> second = Map.of('a', 10, 'c', 11);
        Map<Character, Integer> third = Map.of('a', 3, 'd', 100);

        // First we turn multiple maps into a stream of entries and
        // in the collect method we create a new map and define
        // a function to multiply the entry value when there is a 
        // duplicate entry key.
        Map<Character, Integer> result =
                Stream.of(first, second, third)
                      .flatMap(m -> m.entrySet().stream())
                      .collect(
                              Collectors.toMap(
                                      Map.Entry::getKey,
                                      Map.Entry::getValue,
                                      (value1, value2) -> value1 * value2));

        // The values for duplicate keys are multiplied in the resulting map.
        assert Map.of('a', 60, 'b', 3, 'c', 44, 'd', 100).equals(result);


        // In this sample the value is a Java class Characteristic.
        // The function to apply when a key is duplicate will create
        // a new Characteristic instance contains all values.
        // The resulting map will contain all concatenated characteristic values
        // for each key.
        var langauges =
                Stream.of(Map.of("Java", new Characteristic("jvm")),
                          Map.of("Clojure", new Characteristic("dynamic", "functional")),
                          Map.of("Groovy", new Characteristic("jvm", "dynamic")),
                          Map.of("Clojure", new Characteristic("jvm")),
                          Map.of("Groovy", new Characteristic("dynamic")),
                          Map.of("Java", new Characteristic("static")))
                      .flatMap(m -> m.entrySet().stream())
                      .collect(
                              Collectors.toMap(
                                      Map.Entry::getKey,
                                      Map.Entry::getValue,
                                      (c1, c2) -> c1.addCharateristics(c2.getValues())));

        assert new Characteristic("static", "jvm").equals(langauges.get("Java"));
        assert new Characteristic("dynamic", "functional", "jvm").equals(langauges.get("Clojure"));
        assert new Characteristic("dynamic", "jvm").equals(langauges.get("Groovy"));
    }

    /**
     * Supporting class to store language characteristics.
     */
    static class Characteristic {
        // Store unique characteristic value.
        private Set<String> values = new HashSet<>();

        Characteristic(String characteristic) {
            values.add(characteristic);
        }

        Characteristic(String... characteristics) {
            values.addAll(Arrays.asList(characteristics));
        }

        Characteristic addCharateristics(Set<String> characteristics) {
            values.addAll(characteristics);
            return this;
        }

        Set<String> getValues() {
            return values;
        }

        @Override
        public boolean equals(final Object o) {
            if (this == o) { return true; }
            if (o == null || getClass() != o.getClass()) { return false; }
            final Characteristic that = (Characteristic) o;
            return Objects.equals(values, that.values);
        }

        @Override
        public int hashCode() {
            return Objects.hash(values);
        }
    }
}

Written with Java 15.

January 27, 2021

Java Joy: Transform Stream Of Strings To List Of Key Value Pairs Or Map

Suppose we have a Stream of String objects where two sequential values belong together as a pair. We want to transform the stream into a List where each pair is transformed into a Map object with a key and value. We can write a custom Collector that stores the first String value of a pair. When the next element in the Stream is processed by the Collector a Map object is created with the stored first value and the new value. The new Map is added to the result List.

In the next example we write the ListMapCollector to transform a Stream of paired String values into a List of Map objects:

package mrhaki.streams;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        List<Map<String, String>> pairs =
                Stream.of("language", "Java", "username", "mrhaki")
                      .collect(new ListMapCollector());

        // Result is list of maps: [{language=Java},{username=mrhaki}]
        assert pairs.size() == 2;
        assert pairs.get(0).get("language").equals("Java");
        assert pairs.get(1).get("username").equals("mrhaki");
    }

    private static class ListMapCollector
            implements Collector<String, List<Map<String, String>>, List<Map<String, String>>> {

        private String key;

        /**
         * @return An empty list to add our Map objects to.
         */
        @Override
        public Supplier<List<Map<String, String>>> supplier() {
            return () -> new ArrayList<>();
        }

        /**
         * @return Accumulator to add Map with key and value to the result list.
         */
        @Override
        public BiConsumer<List<Map<String, String>>, String> accumulator() {
            return (list, value) -> {
                if (key != null) {
                    list.add(Map.of(key, value));
                    key = null;
                } else {
                    key = value;
                }
            };
        }

        /**
         * @return Combine two result lists into a single list with all Map objects.
         */
        @Override
        public BinaryOperator<List<Map<String, String>>> combiner() {
            return (list1, list2) -> {
                list1.addAll(list2);
                return list1;
            };
        }

        /**
         * @return Use identity function to return result.
         */
        @Override
        public Function<List<Map<String, String>>, List<Map<String, String>>> finisher() {
            return Function.identity();
        }

        /**
         * @return Collector characteristic to indicate finisher method is identity function.
         */
        @Override
        public Set<Characteristics> characteristics() {
            return Set.of(Characteristics.IDENTITY_FINISH);
        }
    }
}

Another solution could be to turn the Stream with values into a single Map. Each pair of values is a key/value pair in the resulting Map:

package mrhaki.streams;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        Map<String, String> map = 
                Stream.of("language", "Java", "username", "mrhaki")
                      .collect(new MapCollector());
        
        // Result is map: {language=Java,username=mrhaki}
        assert map.size() == 2;
        assert map.get("language").equals("Java");
        assert map.get("username").equals("mrhaki");
    }

    private static class MapCollector
            implements Collector<String, Map<String, String>, Map<String, String>> {

        private String key;

        /**
         * @return An empty map to add keys with values to.
         */
        @Override
        public Supplier<Map<String, String>> supplier() {
            return () -> new HashMap<>();
        }

        /**
         * @return Accumulator to add key and value to the result map.
         */
        @Override
        public BiConsumer<Map<String, String>, String> accumulator() {
            return (map, value) -> {
                if (key != null) {
                    map.put(key, value);
                    key = null;
                } else {
                    key = value;
                }
            };
        }

        /**
         * @return Combine two result maps into a single map.
         */
        @Override
        public BinaryOperator<Map<String, String>> combiner() {
            return (map1, map2) -> {
                map1.putAll(map2);
                return map1;
            };
        }

        /**
         * @return Use identity function to return result.
         */
        @Override
        public Function<Map<String, String>, Map<String, String>> finisher() {
            return Function.identity();
        }

        /**
         * @return Collector characteristic to indicate finisher method is identity function.
         */
        @Override
        public Set<Characteristics> characteristics() {
            return Set.of(Characteristics.IDENTITY_FINISH);
        }
    }
}

Written with Java 15.0.1.

January 25, 2021

Java Joy: Partition Stream By Predicate

The Java Stream API has many useful methods. If we want to partition a stream of objects by a given predicate we can use the partitioningBy() method from the java.util.stream.Collectors package. We must use this method in the collect() method of the stream. The result is a Map with the keys true and false. The objects from the stream that are true for the predicate will end up in the true value list and if the result of the predicate is false the value will end up in the list of values for the false key. The partitionBy method accepts a collector as second parameter. This collector will be applied to the values before they are put in the true or false keys in the result.

In the following example we use the partitioningBy method with different streams:

package mrhaki.stream;

import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        // Let's create an infinitive stream of integers.
        var range = Stream.iterate(0, i -> i + 1);

        // We can partition them by odd and even numbers
        // using a predicate i % 2 == 0, where i is integer from the stream.
        var oddEvenNumbers = 
            range.limit(10)
                  .collect(Collectors.partitioningBy(i -> i % 2 == 0));

        // Even numbers are assigned to the "true" key,
        // odd numbers to the "false" key.
        var odds = oddEvenNumbers.get(false);
        var evens = oddEvenNumbers.get(true);

        assert odds.size() == 5;
        assert odds.equals(List.of(1, 3, 5, 7, 9));
        assert evens.size() == 5;
        assert evens.equals(List.of(0, 2, 4, 6, 8));

        // We use a second argument to sum all odd and even numbers.
        var sumEvenOdd = 
            Stream.iterate(0, i -> i + 1)
                  .limit(100)
                  .collect(
                      Collectors.partitioningBy(i -> i % 2 == 0, 
                                                Collectors.reducing(0, (result, i) -> result += i)));

        assert sumEvenOdd.get(true) == 2450;
        assert sumEvenOdd.get(false) == 2500;
        

        // In the next exmample we start with an immutable map.
        var map = Map.of("language", "Java", "username", "mrhaki", "age", 47);

        // This time we partition on the type of the value where values
        // of type String are assigned to "true" and other types to "false".
        var partitionByStringValue = 
            map.entrySet()
               .stream()
               .collect(Collectors.partitioningBy(entry -> entry.getValue() instanceof String));

        var stringValueEntries = partitionByStringValue.get(true);
        var nonStringValueEntries = partitionByStringValue.get(false);

        assert stringValueEntries.size() == 2;

        var keys = stringValueEntries.stream().map(Map.Entry::getKey).collect(Collectors.toUnmodifiableList());
        var values = stringValueEntries.stream().map(Map.Entry::getValue).collect(Collectors.toUnmodifiableList());
        assert keys.containsAll(List.of("language", "username"));
        assert values.containsAll(List.of("Java", "mrhaki"));

        assert nonStringValueEntries.size() == 1;
        assert nonStringValueEntries.get(0).getKey().equals("age");
        assert nonStringValueEntries.get(0).getValue().equals(47);       
    }
}

Written with Java 15.0.1.

January 20, 2021

Java Joy: Turn Stream Into An Array

The Java Stream API has many useful methods. If we want to transform a Stream to a Java array we can use the toArray method. Without an argument the result is an object array (Object[]), but we can also use an argument to return an array of another type. The easiest way is to use the contructor of the array type we want as method reference. Then the result is an array of the given type with the elements of the stream.

This is very useful if we have a Java Stream and want to use the elements to invoke a method with a variable arguments parameter. In Java we can pass an array object as variable arguments argument to a method. So if we transform the Stream to an array we can invoke the method with that value.

In the following example we see how we transform a Stream with String values to a String[] object. We use this String[] object to invoke a method with a varargs parameter of type String....

package mrhaki.stream;

import java.util.stream.Stream;

public class Sample {

    public static void main(String[] args) {
        // With the .toArray method we can convert a Stream
        // to an array. 
        // We can use the constructur of the array type
        // we want to convert to as method reference to get 
        // the correct array result.
        final var result = 
            Stream.of("Java", "Clojure", "Groovy", "Kotlin")
                  .filter(language -> language.contains("o"))
                  .toArray(String[]::new);

        assert result[0].equals("Clojure");
        assert result[1].equals("Groovy");
        assert result[2].equals("Kotlin");

        // This is useful with varargs parameters as well,
        // as arrays can be used for a varargs parameter.
        assert "Clojure".equals(first(result));
        assert "Clojure".equals(first("Clojure", "Groovy", "Kotlin"));
    }

    private static String first(String... values) {
        return values[0];
    }
}

Written with Java 15.0.1

June 11, 2020

Java Joy: Reapply Function With Stream iterate

In Java we can use the iterate method of the Stream class to create an unbounded stream based on function invocations. We pass to the iterate method an initial value and a function that can be applied to the value. The first element in the unbounded stream is the initial value, the next element is the result of the function invocation with as argument the value from the previous element and this continues for each new element. Suppose we have a function expressed as lambda expression i -> i + 2. When we use this lambda expression with the iterate method and a initial value of 1 we get a stream of 1, 1 -> 1 + 2, 3 -> 3 + 2, ....

As we get an unbounded stream we must for example use limit to get the values we want from the stream. But we can also use an extra argument for the iterate method that is a Predicate definition. The iterate method will provide elements as long as the result of the Predicate is true. This way we the result of the iterate method is a bounded stream.

In the following Java example we use the iterate method with different arguments and lambda expressions:

package mrhaki.stream;

import java.math.BigInteger;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toUnmodifiableList;

public class Iterate {

    public static void main(String[] args) {
        // Create unbounded stream with odd numbers.
        var odds = Stream.iterate(1, i -> i + 2);

        // We use limit(5) to get the first 10 odd numbers from the unbounded stream.
        assert odds.limit(5).collect(toUnmodifiableList()).equals(List.of(1, 3, 5, 7, 9));

        // Create stream with even numbers, but here we use a predicate as
        // second argument to determine that we stop after value 10.
        var evens = Stream.iterate(0, i -> i <= 10, i -> i + 2);
        assert evens.collect(toUnmodifiableList()).equals(List.of(0, 2, 4, 6, 8, 10));


        // Define infinite stream with growing string.
        // The first element is ar, next argh, then arghgh etc.
        var pirate = Stream.iterate("ar", s -> s + "gh");

        // We get the 5-th element for a crumpy pirate.
        var crumpyPirate = pirate.skip(4).findFirst().get();
        assert crumpyPirate.equals("arghghghgh");


        // Function that returns the given amount
        // plus interest of 1.25%.
        UnaryOperator<Double> cumulativeInterest = amount -> amount + (amount * 0.0125);

        // Lazy sequence where each entry is the
        // cumulative amount with interest based
        // on the previous entry.
        // We start our savings at 500.
        var savings = Stream.iterate(500d, cumulativeInterest);

        // First element is start value, so we skip first five elements
        // to get value after 5 years.
        assert savings.skip(5).findFirst().get() == 532.0410768127441;

        
        // Define infinite unbounded stream
        // where each element is the doubled value of the previous element.
        var wheatChessboard = Stream.iterate(BigInteger.valueOf(1), value -> value.add(value));

        // Sum of all values for all chessboard squares is an impressive number.
        var square64 = wheatChessboard.limit(64).reduce(BigInteger::add).get();
        assert square64.equals(new BigInteger("18446744073709551615"));
    }
}

Written with Java 14.

June 10, 2020

Java Joy: Infinite Stream Of Values Or Method Invocations

In Java we can use the generate method of the Stream class to create an infinite stream of values. The values are coming from a Supplier instance we pass as argument to the generate method. The Supplier instance usually will be a lambda expression. To give back a fixed value we simply implement a Supplier that returns the value. We can also have different values when we use a method that returns a different value on each invocation, for example the randomUUID method of the UUID class. When we use such a method we can create the Supplier as method reference: UUID::randomUUID.

The generate method returns an unbounded stream. We must use methods like limit and takeWhile to get a bounded stream again. We must use findFirst or findAny to terminate the unbounded stream and get a value.

In the following example we use the generate method with a Supplier that returns a repeating fixed String value and some different values from invoking the now method of LocalTime:

package mrhaki.stream;

import java.time.LocalTime;
import java.util.List;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toUnmodifiableList;

public class Generate {
    
    public static void main(String[] args) {
        // Create a infinite stream where each item is the string value "Java".
        // We let the supplier function return a fixed value "Java".
        assert Stream.generate(() -> "Java")
                     .limit(4)
                     .collect(toUnmodifiableList()).equals(List.of("Java", "Java", "Java", "Java"));

        // Create an infinite stream of function invocations of LocalTime.now().
        // The supplier function returns a different value for each invocation.
        var currentTimes = Stream.generate(LocalTime::now)
                                 .limit(2)
                                 .collect(toUnmodifiableList());
        assert currentTimes.get(0).isBefore(currentTimes.get(1));

        // Create a list of 100 time values where each value should be later than the next.
        var timeSeries = Stream.generate(LocalTime::now).limit(100).collect(toUnmodifiableList());
        assert latestTime(timeSeries).equals(timeSeries.get(timeSeries.size() - 1));
    }

    /**
     * Get the latest time from a serie of time values.
     *
     * @param times List with time values.
     * @return Latest time value from the collection.
     */
    private static LocalTime latestTime(List<LocalTime> times) {
        return times.stream().reduce((acc, time) -> {
            if (acc.isAfter(time)) { return acc; } else { return time; }
        }).get();
    }
}

Written with Java 14.

September 10, 2019

Java Joy: Transform Elements In Stream Using a Collector

Using the Stream API and the map method we can transform elements in a stream to another object. Instead of using the map method we can also write a custom Collector and transform the elements when we use the collect method as terminal operation of the stream.

First we have an example where we transform String value using the map method:

package mrhaki;

import java.util.List;
import java.util.stream.Collectors;

public class CollectorString {
    public static void main(String[] args) {
        final var items = List.of("JFall", "JavaZone", "CodeOne");

        final List<String> upper =
                items.stream()
                     .map(String::toUpperCase)
                     .collect(Collectors.toUnmodifiableList());
        
        assert upper.equals(List.of("JFALL", "JAVAZONE", "CODEONE"));
    }
}

In our next example we don't use the map method, but we write a custom Collector using the Collector.of method. As first argument we must provide the data structure we want to add elements too, the so-called supplier, which is an ArrayList. The second argument is an accumulator where we add each element from the stream to the list and transform the value. The third argument is the combiner and here we combine multiple List instances to one List instance. The last argument is a finisher and we make an immutable List to be returned.

package mrhaki;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collector;

public class CollectorString1 {
    public static void main(String[] args) {
        final var items = List.of("JFall", "JavaZone", "CodeOne");

        final List<String> upper =
                items.stream()
                     // Use collector to transform values
                     // in the items List.
                     .collect(upperCollect());

        assert upper.equals(List.of("JFALL", "JAVAZONE", "CODEONE"));
    }

    private static Collector<String, ?, List<String>> upperCollect() {
        return Collector.of(
                // First we specify that we want to add
                // each element from the stream to an ArrayList.
                () -> new ArrayList<String>(),

                // Next we add each String value to the list
                // and turn it into an uppercase value.
                (list, value) -> list.add(value.toUpperCase()),

                // Next we get two lists we need to combine,
                // so we add the values of the second list
                // to the first list.
                (first, second) -> { first.addAll(second); return first; },

                // Finally (and optionally) we turn the 
                // ArrayList into an unmodfiable List.
                list -> Collections.unmodifiableList(list));
    }
}

Written with Java 12.

September 9, 2019

Java Joy: Combining Predicates

In Java we can use a Predicate to test if something is true or false. This is especially useful when we use the filter method of the Java Stream API. We can use lambda expressions to define our Predicate or implement the Predicate interface. If we want to combine different Predicate objects we can use the or, and and negate methods of the Predicate interfaces. These are default methods of the interface and will return a new Predicate.

Let's start with an example where we have a list of String values. We want to filter all values that start with Gr or with M. In our first implementation we use a lambda expression as Predicate and implements both tests in this expression:

package mrhaki;

import java.util.List;
import java.util.stream.Collectors;

public class PredicateComposition1 {
    public static void main(String[] args) {
        final var items = List.of("Groovy", "Gradle", "Grails", "Micronaut", "Java", "Kotlin");

        final List<String> gr8Stuff =
                items.stream()
                     // Use lambda expression with both tests as Predicate.
                     .filter(s -> s.startsWith("Gr") || s.startsWith("M"))
                     .collect(Collectors.toUnmodifiableList());

        assert gr8Stuff.size() == 4 : "gr8Stuff contains 4 items";
        assert gr8Stuff.contains("Groovy");
        assert gr8Stuff.contains("Gradle");
        assert gr8Stuff.contains("Grails");
        assert gr8Stuff.contains("Micronaut");
    }
}

We will rewrite the previous example and introduce the startsWith method that returns a new Predicate. Then in our filter method we use the or method of the Predicate object to combine the two Predicate objects:

package mrhaki;

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PredicateComposition2 {
    public static void main(String[] args) {
        final var items = List.of("Groovy", "Gradle", "Grails", "Micronaut", "Java", "Kotlin");

        final List<String> gr8Stuff =
                items.stream()
                     // Use the Predicate.or method to combine two Predicate objects.
                     .filter(startsWith("Gr").or(startsWith("M")))
                     .collect(Collectors.toUnmodifiableList());

        assert gr8Stuff.size() == 4 : "gr8Stuff contains 4 items";
        assert gr8Stuff.contains("Groovy");
        assert gr8Stuff.contains("Gradle");
        assert gr8Stuff.contains("Grails");
        assert gr8Stuff.contains("Micronaut");
    }

    // Create a predicate to check if String value starts with a given value.
    private static Predicate<String> startsWith(final String begin) {
        return s -> s.startsWith(begin);
    }
}

In the following example we use the negate and and method to find all values that do not start with Gr and with a length less than 8 characters:

package mrhaki;

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PredicateComposition3 {
    public static void main(String[] args) {
        final var items = List.of("Groovy", "Gradle", "Grails", "Micronaut", "Java", "Kotlin");

        final List<String> otherStuff =
                items.stream()
                     // Find all values that do not start with "Gr"
                     // and have less than 8 characters.
                     .filter(startsWith("Gr").negate().and(smallerThan(8)))
                     .collect(Collectors.toUnmodifiableList());

        assert otherStuff.size() == 2 : "otherStuff contains 2 items";
        assert otherStuff.contains("Java");
        assert otherStuff.contains("Kotlin");
    }

    // Create a predicate to check if String value starts with a given value.
    private static Predicate<String> startsWith(final String begin) {
        return s -> s.startsWith(begin);
    }

    // Create a predicate to check if String value has 
    // less characters than the given size.
    private static Predicate<String> smallerThan(final int size) {
        return s -> size >= s.length();
    }
}

In our previous example we can replace the negate method call on our predicate with the static Predicate.not method. The predicate is than an argument and is just another way to express the same predicate:

package mrhaki;

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PredicateComposition4 {
    public static void main(String[] args) {
        final var items = List.of("Groovy", "Gradle", "Grails", "Micronaut", "Java", "Kotlin");

        final List<String> otherStuff =
                items.stream()
                     // Find all values that do not start with "Gr",
                     // using Predicate.not instead of negate, 
                     // and have less than 8 characters.
                     .filter(Predicate.not(startsWith("Gr")).and(smallerThan(8)))
                     .collect(Collectors.toUnmodifiableList());

        assert otherStuff.size() == 2 : "otherStuff contains 2 items";
        assert otherStuff.contains("Java");
        assert otherStuff.contains("Kotlin");
    }

    // Create a predicate to check if String value starts with a given value.
    private static Predicate<String> startsWith(final String begin) {
        return s -> s.startsWith(begin);
    }

    // Create a predicate to check if String value has 
    // less characters than the given size.
    private static Predicate<String> smallerThan(final int size) {
        return s -> size >= s.length();
    }
}

Written with Java 12.