Functional Programming - What you need to know

jcarlosvale

Joao Carlos Sousa do Vale

Posted on May 13, 2022

Functional Programming - What you need to know

Functional Programming

Introduction

  • focusing on the Streams API.
  • a functional interface has exactly one abstract method.
    • java.util.function package.
Functional interface Return type Method name # of parameters
Supplier T get() 0
Consumer void accept(T) 1
BiConsumer void accept(T,U) 2 (T, U)
Predicate boolean test(T) 1 (T)
BiPredicate boolean test(T,U) 2 (T, U)
Function R apply(T) 1 (T)
BiFunction R apply(T,U) 2 (T, U)
UnaryOperator T apply(T) 1 (T)
BinaryOperator T apply(T,T) 2 (T, T)

IMPLEMENTING SUPPLIER

  • A Supplier is used when you want to generate or supply values without taking any input.
    • is often used when constructing new objects.
@FunctionalInterface
public interface Supplier<T> {
   T get();
}
Enter fullscreen mode Exit fullscreen mode
Supplier<LocalDate> s1 = () -> LocalDate.now();

LocalDate d1 = s1.get();

System.out.println(d1);
Enter fullscreen mode Exit fullscreen mode

IMPLEMENTING CONSUMER AND BICONSUMER

  • You use a Consumer when you want to do something with a parameter but not return anything.
  • BiConsumer does the same thing except that it takes two parameters.
@FunctionalInterface
public interface Consumer<T> {
   void accept(T t);
}

@FunctionalInterface
public interface BiConsumer<T, U> {
   void accept(T t, U u);
}
Enter fullscreen mode Exit fullscreen mode
Consumer<String> c1 = x -> System.out.println(x);

c1.accept("Annie");
Enter fullscreen mode Exit fullscreen mode
var map = new HashMap<String, Integer>();
BiConsumer<String, Integer> b1 = (k, v) -> map.put(k, v);

b1.accept("chicken", 7);
b1.accept("chick", 1);

System.out.println(map);
Enter fullscreen mode Exit fullscreen mode

IMPLEMENTING PREDICATE AND BIPREDICATE

  • Predicate is often used when filtering or matching.
  • A BiPredicate is just like a Predicate except that it takes two parameters instead of one.
@FunctionalInterface
public interface Predicate<T> {
   boolean test(T t);
}

@FunctionalInterface
public interface BiPredicate<T, U> {
   boolean test(T t, U u);
}
Enter fullscreen mode Exit fullscreen mode
Predicate<String> p1 = x -> x.isEmpty();

System.out.println(p1.test(""));  // true
Enter fullscreen mode Exit fullscreen mode
BiPredicate<String, String> b1 = (string, prefix) -> string.startsWith(prefix);

System.out.println(b1.test("chicken", "chick"));  // true
Enter fullscreen mode Exit fullscreen mode

IMPLEMENTING FUNCTION AND BIFUNCTION

  • A Function is responsible for turning one parameter into a value of a potentially different type and returning it.
  • a BiFunction is responsible for turning two parameters into a value and returning it.
@FunctionalInterface
public interface Function<T, R> {
   R apply(T t);
}

@FunctionalInterface
public interface BiFunction<T, U, R> {
   R apply(T t, U u);
}
Enter fullscreen mode Exit fullscreen mode
Function<String, Integer> f1 = x -> x.length();

System.out.println(f1.apply("cluck")); // 5
Enter fullscreen mode Exit fullscreen mode
BiFunction<String, String, String> b1 = (string, toAdd) -> string.concat(toAdd);

System.out.println(b1.apply("baby ", "chick")); // baby chick
Enter fullscreen mode Exit fullscreen mode

IMPLEMENTING UNARYOPERATOR AND BINARYOPERATOR

  • UnaryOperator and BinaryOperator require all type parameters to be the same type.
  • A UnaryOperator transforms its value into one of the same type.
  • A BinaryOperator merges two values into one of the same type.
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
  T apply(T t);
}

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
  T apply(T t1, T t2);
}
Enter fullscreen mode Exit fullscreen mode
UnaryOperator<String> u1 = x -> x.toUpperCase();

System.out.println(u1.apply("chirp"));  // CHIRP
Enter fullscreen mode Exit fullscreen mode
BinaryOperator<String> b1 = (string, toAdd) -> string.concat(toAdd);

System.out.println(b1.apply("baby ", "chick")); // baby chick
Enter fullscreen mode Exit fullscreen mode

CONVENIENCE METHODS ON FUNCTIONAL INTERFACES

  • Several of the common functional interfaces provide a number of helpful default methods.
Interface instance Method return type Method name Method parameters
Consumer Consumer andThen() Consumer
Function Function andThen() Function
Function Function compose() Function
Predicate Predicate and() Predicate
Predicate Predicate negate()
Predicate Predicate or() Predicate
Predicate<String> egg = s -> s.contains("egg");
Predicate<String> brown = s -> s.contains("brown");

Predicate<String> brownEggs = egg.and(brown);
Predicate<String> otherEggs = egg.and(brown.negate());
Enter fullscreen mode Exit fullscreen mode
Consumer<String> c1 = x -> System.out.print("1: " + x);
Consumer<String> c2 = x -> System.out.print(",2: " + x);

Consumer<String> combined = c1.andThen(c2);
combined.accept("Annie");              // 1: Annie,2: Annie
Enter fullscreen mode Exit fullscreen mode
Function<Integer, Integer> before = x -> x + 1;
Function<Integer, Integer> after = x -> x * 2;

Function<Integer, Integer> combined = after.compose(before);
System.out.println(combined.apply(3));   // 8
Enter fullscreen mode Exit fullscreen mode

Optional

  • An Optional is created using a factory.
    • Think of an Optional as a box that might have something in it or might instead be empty
Optional.empty()
Optional.of(<value>)
Enter fullscreen mode Exit fullscreen mode

CREATING AN OPTIONAL

10: public static Optional<Double> average(int… scores) {
11:    if (scores.length == 0) return Optional.empty();
12:    int sum = 0;
13:    for (int score: scores) sum += score;
14:    return Optional.of((double) sum / scores.length);
15: }

...

      System.out.println(average(90, 100)); // Optional[95.0]
      System.out.println(average());        // Optional.empty

...

20: Optional<Double> opt = average(90, 100);
21: if (opt.isPresent())
22:    System.out.println(opt.get()); // 95.0
Enter fullscreen mode Exit fullscreen mode
  • We'd get an exception since there is no value inside the Optional.
    • java.util.NoSuchElementException: No value present
26: Optional<Double> opt = average();
27: System.out.println(opt.get()); // NoSuchElementException
Enter fullscreen mode Exit fullscreen mode
  • it is common to want to use empty() when the value is null.
    • You can do this with an if statement or ternary operator ( ? :) .
Optional o = (value == null) ? Optional.empty() : Optional.of(value);
Enter fullscreen mode Exit fullscreen mode

Or

Optional o = Optional.ofNullable(value);
Enter fullscreen mode Exit fullscreen mode
Method When Optional is empty When Optional contains a value
get() Throws an exception Returns value
ifPresent(Consumer c) Does nothing Calls Consumer with value
isPresent() Returns false Returns true
orElse(T other) Returns other parameter Returns value
orElseGet(Supplier s) Returns result of calling Supplier Returns value
orElseThrow() Throws NoSuchElementException Returns value
orElseThrow(Supplier s) Throws exception created by calling Supplier Returns value
  • ifPresent() -> You can think of it as an if statement with no else.
Optional<Double> opt = average(90, 100);
opt.ifPresent(System.out::println);
Enter fullscreen mode Exit fullscreen mode

DEALING WITH AN EMPTY OPTIONAL

30: Optional<Double> opt = average();
31: System.out.println(opt.orElse(Double.NaN));
32: System.out.println(opt.orElseGet(() -> Math.random()));
Enter fullscreen mode Exit fullscreen mode
NaN
0.49775932295380165
Enter fullscreen mode Exit fullscreen mode
30: Optional<Double> opt = average();
31: System.out.println(opt.orElseThrow());
Enter fullscreen mode Exit fullscreen mode
  • Without specifying a Supplier for the exception, Java will throw a NoSuchElementException.
Exception in thread "main" java.util.NoSuchElementException: 
   No value present
   at java.base/java.util.Optional.orElseThrow(Optional.java:382)
Enter fullscreen mode Exit fullscreen mode
30: Optional<Double> opt = average();
31: System.out.println(opt.orElseThrow(
32:    () -> new IllegalStateException()));
Enter fullscreen mode Exit fullscreen mode
Exception in thread "main" java.lang.IllegalStateException
   at optionals.Methods.lambda$orElse$1(Methods.java:30)
   at java.base/java.util.Optional.orElseThrow(Optional.java:408)
Enter fullscreen mode Exit fullscreen mode

Using Streams

  • A stream in Java is a sequence of data.
  • A stream pipeline consists of the operations that run on a stream to produce a result.

UNDERSTANDING THE PIPELINE FLOW

  • stream pipeline as an assembly line in a factory.

    • stream operations
  • There are three parts to a stream pipeline

    • source: Where the stream comes from
    • intermediate operations: Transforms the stream into another one.
    • terminal operation: Actually produces a result.
  • The stream is no longer valid after a terminal operation completes.

CREATING STREAM SOURCES

  • In Java, the streams are represented by the Stream interface, defined in the java.util.stream package.

Creating Finite Streams

11: Stream<String> empty = Stream.empty();          // count = 0
12: Stream<Integer> singleElement = Stream.of(1);   // count = 1
13: Stream<Integer> fromArray = Stream.of(1, 2, 3); // count = 3
14: var list = List.of("a", "b", "c");
15: Stream<String> fromList = list.stream();
Enter fullscreen mode Exit fullscreen mode

Creating Infinite Streams

7: Stream<Double> randoms = Stream.generate(Math::random);
18: Stream<Integer> oddNumbers = Stream.iterate(1, n -> n + 2);
Enter fullscreen mode Exit fullscreen mode
  • overloaded example of iterate()
19: Stream<Integer> oddNumberUnder100 = Stream.iterate(
20:    1,                // seed
21:    n -> n < 100,     // Predicate to specify when done
22:    n -> n + 2);      // UnaryOperator to get next value
Enter fullscreen mode Exit fullscreen mode
Method Finite or infinite? Notes
Stream.empty() Finite Creates Stream with zero elements
Stream.of(varargs) Finite Creates Stream with elements listed
coll.stream() Finite Creates Stream from a Collection
coll.parallelStream() Finite Creates Stream from a Collection where the stream can run in parallel
Stream.generate(supplier) Infinite Creates Stream by calling the Supplier for each element upon request
Stream.iterate(seed, unaryOperator) Infinite Creates Stream by using the seed for the first element and then calling the UnaryOperator for each subsequent element upon request
Stream.iterate(seed, predicate, unaryOperator) Finite or infinite Creates Stream by using the seed for the first element and then calling the UnaryOperator for each subsequent element upon request. Stops if the Predicate returns false

USING COMMON TERMINAL OPERATIONS

  • You can perform a terminal operation without any intermediate operations but not the other way around.
  • Reductions are a special type of terminal operation where all the contents of the stream are combined into a single primitive or Object.
Method What happens for infinite streams Return value Reduction
count() Does not terminate long Yes
min()
max()
Does not terminate Optional Yes
findAny()
findFirst()
Terminates Optional No
allMatch()
anyMatch()
noneMatch()
Sometimes terminates boolean No
forEach() Does not terminate void No
reduce() Does not terminate Varies Yes
collect() Does not terminate Varies Yes

count()

  • The count() method determines the number of elements in a finite stream.
long count()
Enter fullscreen mode Exit fullscreen mode
Stream<String> s = Stream.of("monkey", "gorilla", "bonobo");
System.out.println(s.count());   // 3
Enter fullscreen mode Exit fullscreen mode

min() and max()

  • The min() and max() methods allow you to pass a custom comparator and find the smallest or largest value in a finite stream according to that sort order.
Optional<T> min(Comparator<? super T> comparator)
Optional<T> max(Comparator<? super T> comparator)
Enter fullscreen mode Exit fullscreen mode
Stream<String> s = Stream.of("monkey", "ape", "bonobo");
Optional<String> min = s.min((s1, s2) -> s1.length()-s2.length());
min.ifPresent(System.out::println); // ape
Enter fullscreen mode Exit fullscreen mode
  • Returns an optional because we have cases where is not possible to find a min or max.
Optional<?> minEmpty = Stream.empty().min((s1, s2) -> 0);
System.out.println(minEmpty.isPresent()); // false
Enter fullscreen mode Exit fullscreen mode

findAny() and findFirst()

  • The findAny() and findFirst() methods return an element of the stream unless the stream is empty.
    • If the stream is empty, they return an empty Optional.
  • can terminate with an infinite stream.
Optional<T> findAny()
Optional<T> findFirst()
Enter fullscreen mode Exit fullscreen mode
Stream<String> s = Stream.of("monkey", "gorilla", "bonobo");
Stream<String> infinite = Stream.generate(() -> "chimp");

s.findAny().ifPresent(System.out::println);        // monkey (usually)
infinite.findAny().ifPresent(System.out::println); // chimp
Enter fullscreen mode Exit fullscreen mode

allMatch(), anyMatch(), and noneMatch()

  • The allMatch(), anyMatch(), and noneMatch() methods search a stream and return information about how the stream pertains to the predicate.
  • These may or may not terminate for infinite streams.
  • they are not reductions because they do not necessarily look at all of the elements.
boolean anyMatch(Predicate <? super T> predicate)
boolean allMatch(Predicate <? super T> predicate)
boolean noneMatch(Predicate <? super T> predicate)
Enter fullscreen mode Exit fullscreen mode
var list = List.of("monkey", "2", "chimp");
Stream<String> infinite = Stream.generate(() -> "chimp");
Predicate<String> pred = x -> Character.isLetter(x.charAt(0));

System.out.println(list.stream().anyMatch(pred));  // true
System.out.println(list.stream().allMatch(pred));  // false
System.out.println(list.stream().noneMatch(pred)); // false
System.out.println(infinite.anyMatch(pred));       // true
Enter fullscreen mode Exit fullscreen mode

forEach()

  • it is common to iterate over the elements of a stream.
  • on an infinite stream does not terminate.
  • it is not a reduction.
void forEach(Consumer<? super T> action)
Enter fullscreen mode Exit fullscreen mode
tream<String> s = Stream.of("Monkey", "Gorilla", "Bonobo");
s.forEach(System.out::print); // MonkeyGorillaBonobo
Enter fullscreen mode Exit fullscreen mode

reduce()

  • The reduce() method combines a stream into a single object.
T reduce(T identity, BinaryOperator<T> accumulator)

Optional<T> reduce(BinaryOperator<T> accumulator)

<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
Enter fullscreen mode Exit fullscreen mode
  • The identity is the initial value of the reduction
  • The accumulator combines the current result with the current value in the stream.
var array = new String[] { "w", "o", "l", "f" };
var result = "";
for (var s: array) result = result + s;
System.out.println(result); // wolf
Enter fullscreen mode Exit fullscreen mode
Stream<String> stream = Stream.of("w", "o", "l", "f");
String word = stream.reduce("", (s, c) -> s + c);
System.out.println(word); // wolf
Enter fullscreen mode Exit fullscreen mode

OR

Stream<String> stream = Stream.of("w", "o", "l", "f");
String word = stream.reduce("", String::concat);
System.out.println(word); // wolf
Enter fullscreen mode Exit fullscreen mode
Stream<Integer> stream = Stream.of(3, 5, 6);
System.out.println(stream.reduce(1, (a, b) -> a*b));  // 90
Enter fullscreen mode Exit fullscreen mode
  • There are three choices for what is in the Optional.
    • If the stream is empty, an empty Optional is returned.
    • If the stream has one element, it is returned.
    • If the stream has multiple elements, the accumulator is applied to combine them.
BinaryOperator<Integer> op = (a, b) -> a * b;
Stream<Integer> empty = Stream.empty();
Stream<Integer> oneElement = Stream.of(3);
Stream<Integer> threeElements = Stream.of(3, 5, 6);

empty.reduce(op).ifPresent(System.out::println);         // no output
oneElement.reduce(op).ifPresent(System.out::println);    // 3
threeElements.reduce(op).ifPresent(System.out::println); // 90
Enter fullscreen mode Exit fullscreen mode
  • The third method signature is used when we are dealing with different types.
Stream<String> stream = Stream.of("w", "o", "l", "f!");
int length = stream.reduce(0, (i, s) -> i+s.length(), (a, b) -> a+b);
System.out.println(length); // 5
Enter fullscreen mode Exit fullscreen mode
  • i is Integer and s is a String.
  • combiner used in parallel streams.

collect()

  • is a special type of reduction called a mutable reduction.
<R> R collect(Supplier<R> supplier, 
   BiConsumer<R, ? super T> accumulator, 
   BiConsumer<R, R> combiner)

<R,A> R collect(Collector<? super T, A,R> collector)
Enter fullscreen mode Exit fullscreen mode
Stream<String> stream = Stream.of("w", "o", "l", "f");

StringBuilder word = stream.collect(
   StringBuilder::new,
   StringBuilder::append, 
   StringBuilder::append)

System.out.println(word); // wolf
Enter fullscreen mode Exit fullscreen mode
  • Combiner used in parallel streams;
Stream<String> stream = Stream.of("w", "o", "l", "f");

TreeSet<String> set = stream.collect(
   TreeSet::new, 
   TreeSet::add,
   TreeSet::addAll);

System.out.println(set); // [f, l, o, w]
Enter fullscreen mode Exit fullscreen mode
Stream<String> stream = Stream.of("w", "o", "l", "f");
TreeSet<String> set = 
   stream.collect(Collectors.toCollection(TreeSet::new));
System.out.println(set); // [f, l, o, w]
Enter fullscreen mode Exit fullscreen mode
Stream<String> stream = Stream.of("w", "o", "l", "f");
Set<String> set = stream.collect(Collectors.toSet());
System.out.println(set); // [f, w, l, o]
Enter fullscreen mode Exit fullscreen mode

USING COMMON INTERMEDIATE OPERATIONS

  • an intermediate operation produces a stream as its result.

filter()

  • The filter() method returns a Stream with elements that match a given expression.
Stream<T> filter(Predicate<? super T> predicate)
Enter fullscreen mode Exit fullscreen mode
Stream<String> s = Stream.of("monkey", "gorilla", "bonobo");
s.filter(x -> x.startsWith("m"))
   .forEach(System.out::print); // monkey
Enter fullscreen mode Exit fullscreen mode

distinct()

  • The distinct() method returns a stream with duplicate values removed.
Stream<T> distinct()
Enter fullscreen mode Exit fullscreen mode
Stream<String> s = Stream.of("duck", "duck", "duck", "goose");
s.distinct()
   .forEach(System.out::print); // duckgoose
Enter fullscreen mode Exit fullscreen mode

limit() and skip()

  • The limit() and skip() methods can make a Stream smaller, or they could make a finite stream out of an infinite stream.
Stream<T> limit(long maxSize)
Stream<T> skip(long n)
Enter fullscreen mode Exit fullscreen mode
Stream<Integer> s = Stream.iterate(1, n -> n + 1);
s.skip(5)
   .limit(2)
   .forEach(System.out::print); // 67
Enter fullscreen mode Exit fullscreen mode

map()

  • The map() method creates a one‐to‐one mapping from the elements in the stream to the elements of the next step in the stream.
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
Enter fullscreen mode Exit fullscreen mode
Stream<String> s = Stream.of("monkey", "gorilla", "bonobo");
s.map(String::length)
   .forEach(System.out::print); // 676
Enter fullscreen mode Exit fullscreen mode

flatMap()

  • The flatMap() method takes each element in the stream and makes any elements it contains top‐level elements in a single stream.
<R> Stream<R> flatMap(
   Function<? super T, ? extends Stream<? extends R>> mapper)
Enter fullscreen mode Exit fullscreen mode
List<String> zero = List.of();
var one = List.of("Bonobo");
var two = List.of("Mama Gorilla", "Baby Gorilla");
Stream<List<String>> animals = Stream.of(zero, one, two);

animals.flatMap(m -> m.stream())
   .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode
Bonobo
Mama Gorilla
Baby Gorilla
Enter fullscreen mode Exit fullscreen mode

sorted()

  • The sorted() method returns a stream with the elements sorted.
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
Enter fullscreen mode Exit fullscreen mode
Stream<String> s = Stream.of("brown-", "bear-");
s.sorted()
   .forEach(System.out::print); // bear-brown
Enter fullscreen mode Exit fullscreen mode
Stream<String> s = Stream.of("brown bear-", "grizzly-");
s.sorted(Comparator.reverseOrder())
   .forEach(System.out::print); // grizzly-brown bear-
Enter fullscreen mode Exit fullscreen mode

peek()

  • it allows us to perform a stream operation without actually changing the stream.
Stream<T> peek(Consumer<? super T> action)
Enter fullscreen mode Exit fullscreen mode
var stream = Stream.of("black bear", "brown bear", "grizzly");
long count = stream.filter(s -> s.startsWith("g"))
   .peek(System.out::println).count();              // grizzly
System.out.println(count);                          // 1
Enter fullscreen mode Exit fullscreen mode

PUTTING TOGETHER THE PIPELINE

var list = List.of("Toby", "Anna", "Leroy", "Alex");
list.stream()
   .filter(n -> n.length() == 4)
   .sorted()
   .limit(2)
   .forEach(System.out::println); 
Enter fullscreen mode Exit fullscreen mode
Stream.generate(() -> "Elsa")
   .filter(n -> n.length() == 4)
   .limit(2)
   .sorted()
   .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Working with Primitive Streams

CREATING PRIMITIVE STREAMS

  • IntStream: Used for the primitive types int, short, byte, and char
  • LongStream: Used for the primitive type long
  • DoubleStream: Used for the primitive types double and float
Method Primitive stream Description
OptionalDouble average() IntStream
LongStream
DoubleStream
The arithmetic mean of the elements
Stream<T> boxed() IntStream
LongStream
DoubleStream
A Stream where T is the wrapper class associated with the primitive value
OptionalInt max()
OptionalLong max()
OptionalDouble max()
IntStream
LongStream
DoubleStream
The maximum element of the stream
OptionalInt min()
OptionalLong min()
OptionalDouble min()
IntStream
LongStream
DoubleStream
The minimum element of the stream
IntStream range(int a, int b)
LongStream range(long a, long b)
IntStream
LongStream
Returns a primitive stream from a (inclusive) to b (exclusive)
IntStream rangeClosed(int a, int b)
LongStream rangeClosed(long a, long b)
IntStream
LongStream
Returns a primitive stream from a (inclusive) to b (inclusive)
int sum()
long sum()
double sum()
IntStream
LongStream
DoubleStream
Returns the sum of the elements in the stream
IntSummaryStatistics summaryStatistics()
LongSummaryStatistics summaryStatistics()
DoubleSummaryStatistics summaryStatistics()
IntStream
LongStream
DoubleStream
Returns an object containing numerous stream statistics such as the average, min, max, etc.

Table - Common primitive stream methods

  • Examples:

  • Creating streams

DoubleStream empty = DoubleStream.empty();

DoubleStream oneValue = DoubleStream.of(3.14);
oneValue.forEach(System.out::println);

DoubleStream varargs = DoubleStream.of(1.0, 1.1, 1.2);
varargs.forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode
3.14
1.0
1.1
1.2
Enter fullscreen mode Exit fullscreen mode
  • Creating infinite streams
var random = DoubleStream.generate(Math::random);
var fractions = DoubleStream.iterate(.5, d -> d / 2);
random.limit(3).forEach(System.out::println);
fractions.limit(3).forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode
0.07890654781186413
0.28564363465842346
0.6311403511266134
0.5
0.25
0.125
Enter fullscreen mode Exit fullscreen mode
  • Using range
IntStream range = IntStream.range(1, 6);
range.forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode
IntStream rangeClosed = IntStream.rangeClosed(1, 5);
rangeClosed.forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

MAPPING STREAMS

  • Another way to create a primitive stream is by mapping from another stream type.
Source stream class To create Stream To create DoubleStream To create IntStream To create LongStream
Stream map() mapToDouble() mapToInt() mapToLong()
DoubleStream mapToObj() map() mapToInt() mapToLong()
IntStream mapToObj() mapToDouble() map() mapToLong()
LongStream mapToObj() mapToDouble() mapToInt() map()

Mapping methods between types of streams

  • example
Stream<String> objStream = Stream.of("penguin", "fish");
IntStream intStream = objStream.mapToInt(s -> s.length());
Enter fullscreen mode Exit fullscreen mode
Source stream class To create Stream To create DoubleStream To create IntStream To create LongStream
Stream Function ToDoubleFunction ToIntFunction ToLongFunction
DoubleStream Double Function DoubleUnary Operator DoubleToInt Function DoubleToLong Function
IntStream IntFunction IntToDouble Function IntUnary Operator IntToLong Function
LongStream Long Function LongToDouble Function LongToInt Function LongUnary Operator

Function parameters when mapping between types of streams

  • flatmap() on primitive streams has a different method name
var integerList = new ArrayList<Integer>();
IntStream ints = integerList.stream()
.flatMapToInt(x -> IntStream.of(x));
DoubleStream doubles = integerList.stream()
.flatMapToDouble(x -> DoubleStream.of(x));
LongStream longs = integerList.stream()
.flatMapToLong(x -> LongStream.of(x));
Enter fullscreen mode Exit fullscreen mode

USING OPTIONAL WITH PRIMITIVE STREAMS

var stream = IntStream.rangeClosed(1,10);
OptionalDouble optional = stream.average();

optional.ifPresent(System.out::println);                  // 5.5
System.out.println(optional.getAsDouble());               // 5.5
System.out.println(optional.orElseGet(() -> Double.NaN)); // 5.5
Enter fullscreen mode Exit fullscreen mode
  • OptionalDouble is for a primitive and Optional is for the Double wrapper class.
  • getAsDouble() rather than get().
  • orElseGet() takes a DoubleSupplier instead of a Supplier.
OptionalDouble OptionalInt OptionalLong
Getting as a primitive getAsDouble() getAsInt() getAsLong()
orElseGet() parameter type DoubleSupplier IntSupplier LongSupplier
Return type of max() and min() OptionalDouble OptionalInt OptionalLong
Return type of sum() double int long
Return type of average() OptionalDouble OptionalDouble OptionalDouble

Function parameters when mapping between types of streams

  • The sum() method does not return an optional.
  • The min() method returns an OptionalDouble.
5: LongStream longs = LongStream.of(5, 10);
6: long sum = longs.sum();
7: System.out.println(sum);     // 15
8: DoubleStream doubles = DoubleStream.generate(() -> Math.PI);
9: OptionalDouble min = doubles.min(); // runs infinitely
Enter fullscreen mode Exit fullscreen mode

SUMMARIZING STATISTICS

private static int range(IntStream ints) {
   IntSummaryStatistics stats = ints.summaryStatistics();
   if (stats.getCount() == 0) throw new RuntimeException();
   return stats.getMax()-stats.getMin();
}
Enter fullscreen mode Exit fullscreen mode
  • Summary statistics include the following:
    • Smallest number (minimum): getMin()
    • Largest number (maximum): getMax()
    • Average: getAverage()
    • Sum: getSum()
    • Number of values: getCount()

LEARNING THE FUNCTIONAL INTERFACES FOR PRIMITIVES

Functional Interfaces for boolean

  • BooleanSupplier
boolean getAsBoolean()
Enter fullscreen mode Exit fullscreen mode
12: BooleanSupplier b1 = () -> true;
13: BooleanSupplier b2 = () -> Math.random()> .5;
14: System.out.println(b1.getAsBoolean());  // true
15: System.out.println(b2.getAsBoolean());  // false
Enter fullscreen mode Exit fullscreen mode

Functional Interfaces for double, int, and long

Common functional interfaces for primitives

Functional interfaces # parameters Return type Single abstract method
DoubleSupplier
IntSupplier
LongSupplier
0 double
int
long
getAsDouble
getAsInt
getAsLong
DoubleConsumer
IntConsumer
LongConsumer
1 (double)
1 (int)
1 (long)
void accept
DoublePredicate
IntPredicate
LongPredicate
1 (double)
1 (int)
1 (long)
boolean test
DoubleFunction<R>
IntFunction<R> LongFunction<R>
1 (double)
1 (int)
1 (long)
R apply
DoubleUnaryOperator
IntUnaryOperator
LongUnaryOperator
1 (double)
1(int)
1(long)
double
int
long
applyAsDouble
applyAsInt
applyAsLong
DoubleBinaryOperator
IntBinaryOperator
LongBinaryOperator
2 (double, double)
2 (int, int)
2 (long, long)
double
int
long
applyAsDouble
applyAsInt
applyAsLong

Primitive‐specific functional interfaces

Functional interfaces # parameters Return type Single abstract method
ToDoubleFunction
ToIntFunction
ToLongFunction
1 (T) double
int
long
applyAsDouble
applyAsInt
applyAsLong
ToDoubleBiFunction
ToIntBiFunction
ToLongBiFunction
2 (T, U) double
int
long
applyAsDouble
applyAsInt
applyAsLong
DoubleToIntFunction
DoubleToLongFunction
IntToDoubleFunction
IntToLongFunction
LongToDoubleFunction
LongToIntFunction
1 (double)
1 (double)
1 (int)
1 (int)
1 (long)
1 (long)
int
long
double
long
double
int
applyAsInt
applyAsLong
applyAsDouble
applyAsLong
applyAsDouble
applyAsInt
ObjDoubleConsumer
ObjIntConsumer
ObjLongConsumer
2 (T, double)
2 (T, int)
2 (T, long)
void accept

COLLECTING RESULTS

  • There are many predefined collectors
    • These collectors are available via static methods on the Collectors interface.

Examples of grouping/partitioning collectors

Collector Description Return value when passed to collect
averagingDouble(ToDoubleFunction f)
averagingInt(ToIntFunction f)
averagingLong(ToLongFunction f)
Calculates the average for our three core primitive types Double
counting() Counts the number of elements Long
groupingBy(Function f)
groupingBy(Function f, Collector dc)
groupingBy(Function f, Supplier s, Collector dc)
Creates a map grouping by the specified function with the optional map type supplier and optional downstream collector Map>
joining(CharSequence cs) Creates a single String using cs as a delimiter between elements if one is specified String
maxBy(Comparator c)
minBy(Comparator c)
Finds the largest/smallest elements Optional
mapping(Function f, Collector dc) Adds another level of collectors Collector
partitioningBy(Predicate p)
partitioningBy(Predicate p, Collector dc)
Creates a map grouping by the specified predicate with the optional further downstream collector Map>
summarizingDouble(ToDoubleFunction f)
summarizingInt(ToIntFunction f)
summarizingLong(ToLongFunction f)
Calculates average, min, max, and so on DoubleSummaryStatistics IntSummaryStatistics LongSummaryStatistics
summingDouble(ToDoubleFunction f)
summingInt(ToIntFunction f)
summingLong(ToLongFunction f)
Calculates the sum for our three core primitive types Double
Integer
Long
toList()
toSet()
Creates an arbitrary type of list or set List
Set
toCollection(Supplier s) Creates a Collection of the specified type Collection
toMap(Function k, Function v)
toMap(Function k, Function v, BinaryOperator m)
toMap(Function k, Function v, BinaryOperator m, Supplier s)
Creates a map using functions to map the keys, values, an optional merge function, and an optional map type supplier Map

Collecting Using Basic Collectors

var ohMy = Stream.of("lions", "tigers", "bears");
String result = ohMy.collect(Collectors.joining(", "));
System.out.println(result); // lions, tigers, bears
Enter fullscreen mode Exit fullscreen mode
var ohMy = Stream.of("lions", "tigers", "bears");
Double result = ohMy.collect(Collectors.averagingInt(String::length));
System.out.println(result); // 5.333333333333333
Enter fullscreen mode Exit fullscreen mode
var ohMy = Stream.of("lions", "tigers", "bears");
TreeSet<String> result = ohMy
   .filter(s -> s.startsWith("t"))
   .collect(Collectors.toCollection(TreeSet::new));
System.out.println(result); // [tigers]
Enter fullscreen mode Exit fullscreen mode

Collecting into Maps

  • you need to specify two functions.
    • The first function tells the collector how to create the key.
    • The second function tells the collector how to create the value.
var ohMy = Stream.of("lions", "tigers", "bears");
Map<String, Integer> map = ohMy.collect(
   Collectors.toMap(s -> s, String::length));
System.out.println(map); // {lions=5, bears=5, tigers=6}
Enter fullscreen mode Exit fullscreen mode
  • In case of store the same key two times, the collector has no idea what to do, it “solves” the problem by throwing an exception and making it our problem.
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Integer, String> map = ohMy.collect(Collectors.toMap(
   String::length, 
   k -> k)); // BAD
Enter fullscreen mode Exit fullscreen mode
Exception in thread "main" 
   java.lang.IllegalStateException: Duplicate key 5
Enter fullscreen mode Exit fullscreen mode
  • we can
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Integer, String> map = ohMy.collect(Collectors.toMap(
   String::length,
   k -> k, 
  (s1, s2) -> s1 + "," + s2));
System.out.println(map);            // {5=lions,bears, 6=tigers}
System.out.println(map.getClass()); // class java.util.HashMap
Enter fullscreen mode Exit fullscreen mode
  • if you want to save in a TreeMap
var ohMy = Stream.of("lions", "tigers", "bears");
TreeMap<Integer, String> map = ohMy.collect(Collectors.toMap(
   String::length, 
   k -> k, 
   (s1, s2) -> s1 + "," + s2,
   TreeMap::new));
System.out.println(map); //         // {5=lions,bears, 6=tigers}
System.out.println(map.getClass()); // class java.util.TreeMap
Enter fullscreen mode Exit fullscreen mode

Collecting Using Grouping, Partitioning, and Mapping

  • suppose that we want to get groups of names by their length.
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Integer, List<String>> map = ohMy.collect(
   Collectors.groupingBy(String::length));
System.out.println(map);    // {5=[lions, bears], 6=[tigers]}
Enter fullscreen mode Exit fullscreen mode
  • Note that the function you call in groupingBy() cannot return null. It does not allow null keys.

  • Suppose that we don't want a List as the value in the map and prefer a Set instead.

var ohMy = Stream.of("lions", "tigers", "bears");
Map<Integer, Set<String>> map = ohMy.collect(
   Collectors.groupingBy(
      String::length, 
      Collectors.toSet()));
System.out.println(map);    // {5=[lions, bears], 6=[tigers]}
Enter fullscreen mode Exit fullscreen mode
  • We can even change the type of Map returned through yet another parameter.
var ohMy = Stream.of("lions", "tigers", "bears");
TreeMap<Integer, Set<String>> map = ohMy.collect(
   Collectors.groupingBy(
      String::length, 
      TreeMap::new, 
      Collectors.toSet()));
System.out.println(map); // {5=[lions, bears], 6=[tigers]}
Enter fullscreen mode Exit fullscreen mode
  • other option
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Integer, Long> map = ohMy.collect(
   Collectors.groupingBy(
      String::length, 
      Collectors.counting()));
System.out.println(map);    // {5=2, 6=1}
Enter fullscreen mode Exit fullscreen mode
  • Suppose that we wanted to get the first letter of the first animal alphabetically of each length.
    • mapping() collector that lets us go down a level and add another collector.
    • takes two parameters: the function for the value and how to group it further.
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Integer, Optional<Character>> map = ohMy.collect(
   Collectors.groupingBy(
      String::length,
      Collectors.mapping(
         s -> s.charAt(0), 
         Collectors.minBy((a, b) -> a -b))));
System.out.println(map);    // {5=Optional[b], 6=Optional[t]}
Enter fullscreen mode Exit fullscreen mode
  • Partitioning is a special case of grouping.
    • two possible groups—true and false.
    • unlike groupingBy(), we cannot change the type of Map that gets returned.
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Boolean, List<String>> map = ohMy.collect(
   Collectors.partitioningBy(s -> s.length() <= 5));
System.out.println(map);    // {false=[tigers], true=[lions, bears]}
Enter fullscreen mode Exit fullscreen mode
  • modifying from List to Set
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Boolean, Set<String>> map = ohMy.collect(
   Collectors.partitioningBy(
      s -> s.length() <= 7, 
      Collectors.toSet()));
System.out.println(map);    // {false=[], true=[lions, tigers, bears]}
Enter fullscreen mode Exit fullscreen mode

REFERENCES

Blog Java Streams

💖 💪 🙅 🚩
jcarlosvale
Joao Carlos Sousa do Vale

Posted on May 13, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related