Chapter 15 Functional Programming Flashcards

1
Q

Working with Built‐in Functional Interfaces

A
  • provided in the java.util.function package
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

IMPLEMENTING SUPPLIER

A

A Supplier is used when you want to generate or supply values without taking any input. The Supplier interface is defined as follows:

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
  • create a LocalDate object using the factory method now().
  • The LocalDate::now method reference is used to create a Supplier to assign to an intermediate variable s1.
Supplier<LocalDate> s1 = LocalDate::now;
Supplier<LocalDate> s2 = () -> LocalDate.now();
LocalDate d1 = s1.get();
LocalDate d2 = s2.get();
System.out.println(d1);
System.out.println(d2);
  • A Supplier is often used when constructing new objects.
  • we used a constructor reference to create the object.
  • using generics to declare what type of Supplier we are using.
Supplier<StringBuilder> s1 = StringBuilder::new;
Supplier<StringBuilder> s2 = () -> new StringBuilder();
System.out.println(s1.get());
System.out.println(s2.get());
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q
Supplier<ArrayList<String>> s3 = ArrayList<String>::new;
ArrayList<String> a1 = s3.get();
System.out.println(a1);

What would happen if we tried to print out s3 itself?

System.out.println(s3);
A

The code prints something like this:

functionalinterface.BuiltIns\$\$Lambda$1/0x0000000800066840@4909b8da
  • That’s the result of calling toString() on a lambda.
  • test class is named BuiltIns
  • in a package that we created named functionalinterface.
  • $$, which means that the class doesn’t exist in a class file on the file system. It exists only in memory.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

IMPLEMENTING CONSUMER AND BICONSUMER

A
  • 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.
  • The interfaces are defined as follows:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
// omitted default method
}

@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
// omitted default method
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q
Consumer<String> c1 = System.out::println;
Consumer<String> c2 = x -> System.out.println(x);
c1.accept("Annie");
c2.accept("Annie");
A

This example prints Annie twice.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q
var map = new HashMap<String, Integer>();
BiConsumer<String, Integer> b1 = map::put;
BiConsumer<String, Integer> b2 = (k, v) -> map.put(k, v);
b1.accept("chicken", 7);
b2.accept("chick", 1);
System.out.println(map);
A
  • BiConsumer is called with two parameters.
  • They don’t have to be the same type.
  • The output is {chicken=7, chick=1},
  • which shows that both BiConsumer implementations did get called.
  • When declaring b1, we used an instance method reference on an object since we want to call a method on the local variable map.
  • The code to instantiate b1 is a good bit shorter than the code for b2.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q
var map = new HashMap<String, String>();
BiConsumer<String, String> b1 = map::put;
BiConsumer<String, String> b2 = (k, v) -> map.put(k, v);
b1.accept("chicken", "Cluck");
b2.accept("chick", "Tweep");
System.out.println(map);
A
  • The output is {chicken=Cluck, chick=Tweep},
  • which shows that a BiConsumer can use the same type for both the T and U generic parameters.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

IMPLEMENTING PREDICATE AND BIPREDICATE

A
  • You saw Predicate with removeIf() in Chapter 14.
  • Predicate is often used when filtering or matching.
  • A BiPredicate takes two parameters instead of one.
  • The interfaces are defined as follows:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
// omitted default and static methods
}

@FunctionalInterface
public interface BiPredicate<T, U> {
boolean test(T t, U u);
// omitted default methods
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q
Predicate<String> p1 = String::isEmpty;
Predicate<String> p2 = x -> x.isEmpty();
System.out.println(p1.test("")); // true
System.out.println(p2.test("")); // true
A

This prints true twice.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q
BiPredicate<String, String> b1 = String::startsWith;
BiPredicate<String, String> b2 = (string, prefix) -> string.startsWith(prefix);
System.out.println(b1.test("chicken", "chick")); // true
System.out.println(b2.test("chicken", "chick")); // true
A
  • The method reference includes both the instance variable and parameter for startsWith().
  • This is a good example of how method references save a good bit of typing.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

IMPLEMENTING FUNCTION AND BIFUNCTION

A
  • In Chapter 14, we used Function with the merge() method.
  • A Function is responsible for turning one parameter into a value of a potentially different type and returning it.
  • Similarly, a BiFunction is responsible for turning two parameters into a value and returning it.
  • The interfaces are defined as follows:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
// omitted default and static methods
}

@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
// omitted default method
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q
Function<String, Integer> f1 = String::length;
Function<String, Integer> f2 = x -> x.length();
System.out.println(f1.apply("cluck")); // 5
System.out.println(f2.apply("cluck")); // 5
A
  • This function turns a String into an Int, which is autoboxed into an Integer
  • The types don’t have to be different.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q
BiFunction<String, String, String> b1 = String::concat;
BiFunction<String, String, String> b2 = (string, toAdd) -> string.concat(toAdd);
System.out.println(b1.apply("baby ", "chick")); // baby chick
System.out.println(b2.apply("baby ", "chick")); // baby chick
A
  • The first two types in the BiFunction are the input types.
  • The third is the result type.
  • For the method reference, the first parameter is the instance that concat() is called on, and the second is passed to concat().
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

CREATING YOUR OWN FUNCTIONAL INTERFACES

A
  • Java provides a built‐in interface for functions with one or two parameters.
  • You could create a functional interface such as this:

3 paramters:

@FunctionalInterface
interface TriFunction<T,U,V,R> {
R apply(T t, U u, V v);
}

4 parameters:

@FunctionalInterface
interface QuadFunction<T,U,V,W,R> {
R apply(T t, U u, V v, W w);
}

Remember that you can add any functional interfaces you’d like, and Java matches them when you use lambdas or method references.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

IMPLEMENTING UNARYOPERATOR AND BINARYOPERATOR

A
  • UnaryOperator and BinaryOperator are a special case of a Function.
  • They require all type parameters to be the same type.
  • A UnaryOperator transforms its value into one of the same type.
  • UnaryOperator extends Function.
  • A BinaryOperator merges two values into one of the same type.
  • BinaryOperator extends BiFunction.
  • The interfaces are defined as follows:
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> { }

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
// omitted static methods
}

method signatures look like this:

T apply(T t); // UnaryOperator

T apply(T t1, T t2); // BinaryOperator
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q
UnaryOperator<String> u1 = String::toUpperCase;
UnaryOperator<String> u2 = x -> x.toUpperCase();
System.out.println(u1.apply("chirp")); // CHIRP
System.out.println(u2.apply("chirp")); // CHIRP
A

This prints CHIRP twice.

We don’t need to specify the return type in the generics because UnaryOperator requires it to be the same as the parameter

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q
BinaryOperator<String> b1 = String::concat;
BinaryOperator<String> b2 = (string, toAdd) -> string.concat(toAdd);
System.out.println(b1.apply("baby ", "chick")); // baby chick
System.out.println(b2.apply("baby ", "chick")); // baby chick
A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

CHECKING FUNCTIONAL INTERFACES

What functional interface would you use in these three situations?

  1. Returns a String without taking any parameters
  2. Returns a Boolean and takes a String
  3. Returns an Integer and takes two Integers
A
  1. Supplier<String>
  2. Function<String, Boolean>
    Predicate<String>. Note that a Predicate returns a boolean primitive and not a Boolean object.
  3. BinaryOperator<Integer> or a BiFunction<Integer,Integer,Integer>
    BinaryOperator<Integer> is the better answer of the two since it is more specific.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

What functional interface would you use to fill in the blank for these?

6: \_\_\_\_\_\_\_<List> ex1 = x -> "".equals(x.get(0));
7: \_\_\_\_\_\_\_<Long> ex2 = (Long l) -> System.out.println(l);
8: \_\_\_\_\_\_\_<String, String> ex3 = (s1, s2) -> false;
A
  • Line 6 Predicate
    Since the generic declaration has only one parameter, it is a Predicate.
  • Line 7 passes one Long parameter to the lambda and doesn’t return anything. This tells us that it is a Consumer.
  • Line 8, there are two parameters, so it is a BiPredicate.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

These are meant to be tricky:

6: Function<List<String>> ex1 = x -> x.get(0); // DOES NOT COMPILE
7: UnaryOperator<Long> ex2 = (Long l) -> 3.14; // DOES NOT COMIPLE
8: Predicate ex4 = String::isEmpty; // DOES NOT COMPILE
A
  • Line 6 claims to be a Function. A Function needs to specify two generics—the input parameter type and the return value type. The return value type is missing from line 6, causing the code not to compile.
  • Line 7 is a UnaryOperator, which returns the same type as it is passed in. The example returns a double rather than a Long, causing the code not to compile.
  • Line 8 is missing the generic for Predicate. This makes the parameter that was passed an Object rather than a String. The lambda expects a String because it calls a method that exists on String rather than Object. Therefore, it doesn’t compile.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

CONVENIENCE METHODS ON FUNCTIONAL INTERFACES

A

Several of the common functional interfaces provide a number of helpful default methods.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

It’s a bit long to read, and it contains duplication.
What if we decide the letter e should be capitalized in egg?

Predicate<String> egg = s -> s.contains("egg");
Predicate<String> brown = s -> s.contains("brown");

Predicate<String> brownEggs = s -> s.contains("egg") && s.contains("brown");
Predicate<String> otherEggs = s -> s.contains("egg") && ! s.contains("brown");
A
  • reusing the logic in the original Predicate variables to build two new ones.
  • It’s shorter and clearer what the relationship is between variables.
  • We can also change the spelling of egg in one place, and the other two objects will have new logic because they reference it.
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());
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q
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
A
  • andThen() method, which runs two functional interfaces in sequence.
  • Notice how the same parameter gets passed to both c1 and c2.
  • This shows that the Consumer instances are run in sequence and are independent of each other.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q
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
A
  • compose() method on Function chains functional interfaces.
  • This time the before runs first, turning the 3 into a 4.
  • Then the after runs, doubling the 4 to 8.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
25
**Returning an Optional**
* An Optional is created using a factory. * You can either request an empty Optional or pass a value for the Optional to wrap.
26
**CREATING AN OPTIONAL** ``` 10: public static Optional 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: } ```
* Line 11 returns an empty Optional when we can't calculate an average. **`Optional.empty()`** * Lines 12 and 13 add up the scores. * There is a functional programming way of doing this math, but we will get to that later in the chapter. In fact, the entire method could be written in one line, but that wouldn't teach you how Optional works! * Line 14 creates an Optional to wrap the average. **`Optional.of((double) sum / scores.length)`** When creating an Optional, 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); ``` Java provides a factory method to do the same thing. **`Optional o = Optional.ofNullable(value);`**
27
``` 20: Optional opt = average(90, 100); 21: if (opt.isPresent()) 22: System.out.println(opt.get()); // 95.0 ```
* Line 21 checks whether the Optional actually contains a value. * Line 22 prints it out. if we didn't do the check and the Optional was empty ``` 26: Optional opt = average(); 27: System.out.println(opt.get()); // NoSuchElementException ``` We'd get an exception since there is no value inside the Optional. ``` java.util.NoSuchElementException: No value present ```
28
**DEALING WITH AN EMPTY OPTIONAL**
29
``` 30: Optional opt = average(); 31: System.out.println(opt.orElse(Double.NaN)); 32: System.out.println(opt.orElseGet(() -> Math.random())); ```
This prints something like the following: ``` NaN 0.49775932295380165 ``` * Line 31 shows that you can return a specific value or variable. In our case, we print the “not a number” value. * Line 32 shows using a Supplier to generate a value at runtime to return instead. I'm glad our professors didn't give us a random average, though!
30
``` 30: Optional opt = average(); 31: System.out.println(opt.orElseThrow()); ```
This prints something like the following: ``` Exception in thread "main" java.util.NoSuchElementException: No value present at java.base/java.util.Optional.orElseThrow(Optional.java:382) ``` * Without specifying a Supplier for the exception, Java will throw a NoSuchElementException. * This method was added in Java 10. Remember that the stack trace looks weird because the lambdas are generated rather than named classes.
31
``` 30: Optional opt = average(); 31: System.out.println(opt.orElseThrow( 32: () -> new IllegalStateException())); ```
This prints something like the following: ``` 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) ``` * Line 32 shows using a Supplier to create an exception that should be thrown. Notice that we do not write throw new IllegalStateException(). * The orElseThrow() method takes care of actually throwing the exception when we run it.
32
``` System.out.println(opt.orElseGet( () -> new IllegalStateException())); // DOES NOT COMPILE ```
* The opt variable is an `Optional`. * This means the Supplier must return a Double. * Since this supplier returns an exception, the type does not match.
33
``` Optional opt = average(90, 100); System.out.println(opt.orElse(Double.NaN)); System.out.println(opt.orElseGet(() -> Math.random())); System.out.println(opt.orElseThrow()); ```
It prints out 95.0 three times. Since the value does exist, there is no need to use the “or else” logic.
34
**IS OPTIONAL THE SAME AS NULL?**
Optional is a clear statement in the API that there might not be a value in there.
35
**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.
36
**UNDERSTANDING THE PIPELINE FLOW**
Finite streams infinite streams With streams, the data isn't generated up front—it is created when needed. This is an example of lazy evaluation, which delays execution until necessary. stream operations There are three parts to a stream pipeline * **Source**: Where the stream comes from * **Intermediate operations**: Transforms the stream into another one. There can be as few or as many intermediate operations as you'd like. Since streams use lazy evaluation, the intermediate operations do not run until the terminal operation runs. * **Terminal operation**: Actually produces a result. Since streams can be used only once, the stream is no longer valid after a terminal operation completes. stream pipeline concept * A factory typically has a foreman who oversees the work. * Java serves as the foreman when working with stream pipelines. * This is a really important role, especially when dealing with lazy evaluation and infinite streams. * Think of declaring the stream as giving instructions to the foreman. * As the foreman finds out what needs to be done, he sets up the stations and tells the workers what their duties will be. * The foreman waits until he sees the terminal operation to actually kick off the work. * He also watches the work and stops the line as soon as work is complete.
37
**CREATING STREAM SOURCES**
Stream interface defined in the java.util.stream package.
38
**Creating Finite Streams**
There are a few ways to create them. ``` 11: Stream empty = Stream.empty(); // count = 0 12: Stream singleElement = Stream.of(1); // count = 1 13: Stream fromArray = Stream.of(1, 2, 3); // count = 3 ``` * Line 11 shows how to create an empty stream. * Line 12 shows how to create a stream with a single element. * Line 13 shows how to create a stream from a varargs. You've undoubtedly noticed that there isn't an array on line 13. * The method signature uses varargs, which let you specify an array or individual elements. Java also provides a convenient way of converting a Collection to a stream. ``` 14: var list = List.of("a", "b", "c"); 15: Stream fromList = list.stream(); ``` Line 15 shows that it is a simple method call to create a stream from a list.
39
**CREATING A PARALLEL STREAM**
``` 24: var list = List.of("a", "b", "c"); 25: Stream fromListParallel = list.parallelStream(); ``` some tasks cannot be done in parallel aware that there is a cost in coordinating the work, so for smaller streams, it might be faster to do it sequentially.
40
**Creating Infinite Streams**
``` 17: Stream randoms = Stream.generate(Math::random); 18: Stream oddNumbers = Stream.iterate(1, n -> n + 2); ``` * Line 17 generates a stream of random numbers. How many random numbers? However many you need. If you call randoms.forEach(System.out::println), the program will print random numbers until you kill it. Later in the chapter, you'll learn about operations like limit() to turn the infinite stream into a finite stream. * Line 18 gives you more control. The iterate() method takes a seed or starting value as the first parameter. This is the first element that will be part of the stream. The other parameter is a lambda expression that gets passed the previous value and generates the next value. As with the random numbers example, it will keep on producing odd numbers as long as you need them.
41
What if you wanted just odd numbers less than 100? ``` 19: Stream oddNumberUnder100 = Stream.iterate( 20: 1, // seed 21: n -> n < 100, // Predicate to specify when done 22: n -> n + 2); // UnaryOperator to get next value ```
Java 9 introduced an overloaded version of iterate() This method takes three parameters. Notice how they are separated by commas ( ,) just like all other methods.
42
**Reviewing Stream Creation Methods**
1. **`Stream.empty()`** 2. **`Stream.of(varargs)`** 3. **`coll.stream()`** 4. **`coll.parallelStream()`** 5. **`Stream.generate(supplier)`** 6. **`Stream.iterate(seed, unaryOperator)`** 7. **`Stream.iterate(seed, predicate, unaryOperator)`**
43
**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 of the contents of the stream are combined into a single primitive or Object. 1. count() 2. min() max() 3. findAny() findFirst() 4. allMatch() anyMatch() noneMatch() 5. forEach() 6. reduce() 7. collect()
44
**`count()`s**
The count() method determines the **number of elements in a finite stream.** For an **infinite stream, it never terminates.** The count() method is a **reduction** because it looks at each element in the stream and returns a single value. The method signature is as follows: ``` long count() ``` ex: ``` Stream s = Stream.of("monkey", "gorilla", "bonobo"); System.out.println(s.count()); // 3 ```
45
**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. min() and max() hang on an infinite stream Both methods are reductions because they return a single value after looking at the entire stream. The method signatures are as follows: ``` Optional min(Comparator comparator) Optional max(Comparator comparator) ``` Notice that the code returns an **Optional** rather than the value. ex: ``` Stream s = Stream.of("monkey", "ape", "bonobo"); Optional min = s.min((s1, s2) -> s1.length()-s2.length()); min.ifPresent(System.out::println); // ape ``` ex: ``` Optional minEmpty = Stream.empty().min((s1, s2) -> 0); System.out.println(minEmpty.isPresent()); // false ```
46
What if you need both the min() and max() values of the same stream?
you can't have both stream can have only one terminal operation there are built‐in summary methods for some numeric streams that will calculate a set of values for you.
47
**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. the findAny() method can return any element of the stream. it commonly returns the first element, although this behavior is not guaranteed. the findAny() method is more likely to return a random element when working with parallel streams. These methods are terminal operations but not reductions. The reason is that they sometimes return without processing all of the elements. This means that they return a value based on the stream but do not reduce the entire stream into one value. The method signatures are as follows: ``` Optional findAny() Optional findFirst() ``` ex: ``` Stream s = Stream.of("monkey", "gorilla", "bonobo"); Stream infinite = Stream.generate(() -> "chimp"); s.findAny().ifPresent(System.out::println); // monkey (usually) infinite.findAny().ifPresent(System.out::println); // chimp ```
48
**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. It depends on the data. * Like the find methods, they are not reductions because they do not necessarily look at all of the elements. The method signatures are as follows: ``` boolean anyMatch(Predicate predicate) boolean allMatch(Predicate predicate) boolean noneMatch(Predicate predicate) ``` ex: ``` var list = List.of("monkey", "2", "chimp"); Stream infinite = Stream.generate(() -> "chimp"); Predicate 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 ``` On the infinite stream, one match is found, so the call terminates. If we called allMatch(), it would run until we killed the program.
49
* Remember that **allMatch(), anyMatch(), and noneMatch()** return a **boolean**. * By contrast, the **find methods** return an **Optional** because they return an element of the stream.
50
**forEach()**
calling forEach() on an infinite stream does not terminate. Since there is no return value, it is not a reduction. Before you use it, consider if another approach would be better. The method signature is as follows: ``` void forEach(Consumer action) ``` only terminal operation with a return type of void. If you want something to happen, you have to make it happen in the Consumer. ex: ``` Stream s = Stream.of("Monkey", "Gorilla", "Bonobo"); s.forEach(System.out::print); // MonkeyGorillaBonobo ```
51
**reduce()**
* The reduce() method combines a stream into a single object. * It is a reduction, which means it processes all elements. * The three method signatures are these: ``` T reduce(T identity, BinaryOperator accumulator) Optional reduce(BinaryOperator accumulator) U reduce(U identity, BiFunction accumulator, BinaryOperator combiner) ``` The identity is the initial value of the reduction he accumulator combines the current result with the current value in the stream. ex: ``` var array = new String[] { "w", "o", "l", "f" }; var result = ""; for (var s: array) result = result + s; System.out.println(result); // wolf Stream stream = Stream.of("w", "o", "l", "f"); String word = stream.reduce("", (s, c) -> s + c); System.out.println(word); // wolf Stream stream = Stream.of("w", "o", "l", "f"); String word = stream.reduce("", String::concat); System.out.println(word); // wolf Stream stream = Stream.of(3, 5, 6); System.out.println(stream.reduce(1, (a, b) -> a*b)); // 90 ```
52
In many cases, the identity isn't really necessary, so Java lets us omit it. When you don't specify an identity, an Optional is returned because there might not be any data. 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.
53
``` BinaryOperator op = (a, b) -> a * b; Stream empty = Stream.empty(); Stream oneElement = Stream.of(3); Stream 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 ```
54
collect()
test
55
USING COMMON INTERMEDIATE OPERATIONS
test
56
filter()
test
57
distinct()
test
58
limit() and skip()
test
59
map()
test
60
flatMap()
test
61
sorted()
test
62
peek()
test
63
DANGER: CHANGING STATE WITH PEEK()
test
64
PUTTING TOGETHER THE PIPELINE
test
65
PEEKING BEHIND THE SCENES
test
66
Working with Primitive Streams
test
67
CREATING PRIMITIVE STREAMS
test
68
MAPPING STREAMS
test
69
USING FLATMAP()
test
70
USING OPTIONAL WITH PRIMITIVE STREAMS
test
71
SUMMARIZING STATISTICS
test
72
LEARNING THE FUNCTIONAL INTERFACES FOR PRIMITIVES
test
73
Functional Interfaces for boolean
test
74
Functional Interfaces for double int and long
test
75
Working with Advanced Stream Pipeline Concepts
test
76
LINKING STREAMS TO THE UNDERLYING DATA
test
77
CHAINING OPTIONALS
test
78
CHECKED EXCEPTIONS AND FUNCTIONAL INTERFACES
test
79
COLLECTING RESULTS
test
80
Collecting Using Basic Collectors
test
81
Collecting into Maps
test
82
Collecting Using Grouping Partitioning and Mapping
test
83
DEBUGGING COMPLICATED GENERICS
test