Java Advanced Features: Interfaces & Streams
Java Advanced Features: Interfaces & Streams
Java
Péter Jeszenszky
Faculty of Informatics, University of Debrecen
[Link]@[Link]
2
Java SE 8
●
JDK 8 was released on 18 March 2014.
– See: JDK 8: General Availability
[Link]
.html
●
New features:
– What's New in JDK 8
[Link]
– JDK 8 Features [Link]
●
The most important changes, namely, lambda expressions and
streams, were developed in Project Lambda, a subproject of
OpenJDK.
– See: Project Lambda [Link]
3
Non-abstract Interface Methods:
Evolving Interfaces (1)
●
Problem: how to add new methods to already existing
interfaces?
– When a new method is added to an interface, an implementation
must be provided in each class that implements the interface.
●
In case of a widely used interface it can require a huge amount of work!
●
Solution: default methods and static interface methods
provide a mechanism to add new methods to an interface
that are automatically available in all implementations.
– Moreover, the addition of these methods do not require any
modification or recompilation of existing implementations.
●
This is called binary compatibility.
4
Non-abstract Interface Methods:
Evolving Interfaces (2)
●
Real-world example from Java SE:
– For example, consider the
[Link]<T> interface in Java SE 7:
[Link]
[Link]
public interface Iterable<T> {
Iterator<T> iterator();
5
Non-abstract Interface Methods:
Evolving Interfaces (3)
●
Real-world example from Java SE: (continued)
– Java SE 8 added the forEach(consumer) and
spliterator() default methods to the interface:
public interface Iterable<T> {
Iterator<T> iterator();
} 6
Non-abstract Interface Methods (1)
●
A non-abstract interface method is an interface
method that is declared with one of the modifiers
default, static, or private, and also has a
method body.
– Default methods and static interface methods were
introduced in Java SE 8, private interface methods in
Java SE 9.
● An interface method lacking a private, default,
or static modifier is implicitly abstract.
7
Non-abstract Interface Methods (2)
● The abstract, default, and static modifiers are
mutually exclusive for interface method declarations.
– It is a compile-time error if an interface method declaration
has more than one of these modifiers.
●
It is a compile-time error if an interface method
declaration that contains the private modifier also
contains the abstract or default modifier.
●
However, it is permitted for an interface method
declaration to contain both the private and static
modifiers.
8
Non-abstract Interface Methods:
Default Methods (1)
●
A default method is an instance method declared
in an interface with the default modifier.
– Is also known as a virtual extension method.
●
The method body provides an implementation of
the method for any class that implements the
interface without overriding the method.
●
It is a compile-time error if a default method
overrides a method of the class
[Link].
9
Non-abstract Interface Methods:
Default Methods (2)
●
When an interface extends an interface that contains a default method, it
can do any of the following:
– Not mention the default method at all, which means that the new interface inherits it.
– Redefine the default method, which overrides it.
– Redeclare the default method as abstract, which forces implementing classes to
override it.
●
Similarly, when a class implements an interface that contains a default
method, it can do any of the following:
– Not mention the default method at all, which means that the class inherits the default
method.
– Redefine the default method, which overrides it.
– Redeclare the default method as abstract, which forces subclasses to override it.
(This option is available only if the class is abstract.)
10
Non-abstract Interface Methods:
Default Methods (3)
●
Real-world example from OpenJDK 11:
– See the spliterator() method of the following
interfaces and classes:
● [Link]
[Link]
va/lang/[Link]
● [Link]
[Link]
va/util/[Link]
● [Link]
[Link]
va/util/[Link]
● [Link]
[Link]
va/util/[Link] 11
// [Link]:
public interface Iterable<T> {
// ...
default Spliterator<T> spliterator() {
return [Link](iterator(), 0);
}
} «interface»
java::lang::Iterable
// [Link]:
public interface Collection<E> extends Iterable<E> {
// ...
@Override
default Spliterator<E> spliterator() {
return [Link](this, 0); «interface»
} java::util::Collection
}
// [Link]:
public interface Set<E> extends Collection<E> {
// ... «interface»
@Override java::util::Set
default Spliterator<E> spliterator() {
return [Link](this, [Link]);
}
}
// [Link]: «interface»
public class HashSet<E> extends AbstractSet<E>, implements Set<E>, java::util::HashSet
Cloneable, [Link] {
// ...
public Spliterator<E> spliterator() {
return new [Link]<>(map, 0, -1, 0, 0);
}
12
}
Non-abstract Interface Methods:
Default Methods (5)
●
As an unintended consequence, default
methods enable multiple inheritance.
– Example:
public interface A {
default void someMethod() {
[Link]("[Link]() is called");
}
}
public interface B {
default void someMethod() {
[Link]("[Link]() is called");
}
}
14
Non-abstract Interface Methods:
Default Methods (7)
●
As an unintended consequence, default
methods enable multiple inheritance.
– Example: (continued)
●
In order to fix the error, the class must redefine the
method:
public class SomeClass implements A, B {
@Override
public void someMethod() {
// provide implementation
}
}
15
Non-abstract Interface Methods:
Default Methods (8)
●
As an unintended consequence, default
methods enable multiple inheritance.
– Example: (continued)
●
However, the redefined method can call the default
implementation from any of the declaring interfaces:
public class SomeClass implements A, B {
@Override
public void someMethod() {
[Link]();
}
}
16
Non-abstract Interface Methods:
Default Methods (9)
●
Examples of default methods in Java SE 21:
– [Link]
[Link]
● See, for example, the reversed() method.
– [Link]
[Link]
● See the forEach
(action) and spliterator() methods.
– [Link]
[Link]
● See, for example, the stream() and parallelStream() methods.
– [Link] [Link]
● See, for example, the sort
(comparator) method.
– [Link]
[Link]
● See the dropWhile
(predicate) and takeWhile
(predicate) methods.
– …
17
Non-abstract Interface Methods:
Static Interface Methods (1)
●
A static interface method is a method declared
in an interface with the static modifier.
●
Are not inherited by subinterfaces.
●
Are invoked without reference to a particular
instance, just like a static method in a class.
● It is a compile-time error if the keyword this or
the keyword super occurs in the method body
of a static interface method.
18
Non-abstract Interface Methods:
Static Interface Methods (2)
●
Static interface methods are provided for being
able to add concrete utility methods related to
an interface directly to the interface itself.
– Before Java SE 8, such utility methods could be
provided only in separate utility classes.
19
Non-abstract Interface Methods:
Static Interface Methods (3)
●
Real-world example from OpenJDK 21:
[Link]
[Link]
api/[Link]/java/util/[Link]
package [Link];
} 20
Non-abstract Interface Methods:
Static Interface Methods (4)
● Examples of static interface methods in Java SE 21:
– [Link]
[Link]
til/[Link]
● See, for example, the naturalOrder() and reverseOrder() methods.
– [Link]
[Link]
til/[Link]
● See the copyOf
(collection) and of(...) methods.
– [Link]
[Link]
til/stream/[Link]
● See, for example, the builder(), empty(), and of(...) methods.
– …
21
Non-abstract Interface Methods:
Private Interface Methods (1)
●
A private interface method is a method declared
in an interface with the private modifier.
– The private modifier can be combined with the
static modifier.
●
Are not inherited by subinterfaces.
●
Are provided for sharing code between default
methods and static interface methods.
22
Non-abstract Interface Methods:
Private Interface Methods (2)
●
Example:
public interface Bookshelf {
List<Book> getBooks();
23
Non-abstract Interface Methods:
Private Interface Methods (3)
●
Example: refactored version of the previous interface that uses
a private interface method
public interface Bookshelf {
List<Book> getBooks();
25
[Link] (2)
●
Static methods: ●
Instance methods:
– Optional<T> empty(): – boolean isPresent():
● Returns an empty Optional ●
Returns whether the instance
instance. contains a value.
– Optional<T> of(T value): – T get():
● Returns an Optional instance ●
If the instance contains a value,
with the specified non-null value. returns it, otherwise throws
NoSuchElementException.
– Optional<T> ofNullable(T
value): – T orElse
(T other):
● Returns an Optional instance
●
If the instance contains a value,
containing the specified value, if returns it, otherwise returns
non-null, otherwise returns an other.
empty Optional instance. – …
26
[Link] (3)
●
Primitive-specialized versions:
– [Link]
[Link]
[Link]/java/util/[Link]
– [Link]
[Link]
[Link]/java/util/[Link]
– [Link]
[Link]
[Link]/java/util/[Link]
27
[Link] (4)
●
Example:
Optional<Book> findBook(String isbn) {
// ...
}
28
Functional Interfaces (1)
●
A functional interface is an interface that has just one abstract
method.
– Also known as a Single Abstract Method (SAM) interface or type.
– The single abstract method is called the functional method for the
functional interface.
– A functional interface may still have multiple default, static, and/or private
methods.
●
Default and static interface methods were introduced in Java SE 8, private interface
methods in Java SE 9.
●
See:
– The Java Language Specification, Java SE 21 Edition – Functional
Interfaces.
[Link]
29
Functional Interfaces (2)
● The annotation interface FunctionalInterface is used to
indicate that an interface is meant to be a functional
interface.
– See: [Link]
[Link]
ang/[Link]
●
It is a compile-time error if an interface declaration is
annotated with @FunctionalInterface but is not, in fact,
a functional interface.
●
Because some interfaces are functional incidentally, it is not
necessary or desirable that all declarations of functional
interfaces be annotated with @FunctionalInterface.
30
Functional Interfaces (3)
●
Examples:
@FunctionalInterface
public interface Task {
void perform();
}
@FunctionalInterface
public interface Converter<F, T> {
T convert(F from);
}
31
Built-in Functional Interfaces (1)
●
In Java SE 8, many existing interfaces that are actually functional
have been tagged with the @FunctionalInterface annotation.
– See: Uses of Uses of Annotation Interface
[Link]
[Link]
ass-use/[Link]
– Examples:
● [Link]
[Link]
● [Link]
[Link]
html
● [Link]<T>
[Link]
html
●
…
32
Built-in Functional Interfaces (2)
●
Moreover, Java SE 8 introduced many new functional
interfaces, see the [Link] package.
– See: [Link]
[Link]
se/java/util/function/[Link]
– Examples:
● [Link]<T,R>
● [Link]<T>
● [Link]<T>
● [Link]<T>
●
…
33
Built-in Functional Interfaces (3)
● [Link]<T,
R>
– Represents a function that accepts one argument and produces a result.
– The functional method is R apply
(T t).
●
It applies the function to the given argument.
– Non-abstract methods:
● andThen(after): returns a composed function that first applies the function
represented by the instance to its input, and then applies the after function to the
result.
● compose (before): returns a composed function that first applies the before function
to its input, and then applies the function represented by the instance to the result.
● identity(): returns a function that always returns its input argument.
– See:
[Link]
/[Link]
34
Built-in Functional Interfaces (4)
● [Link]<T>
– Represents a predicate (boolean-valued function) of one argument.
– The functional method is boolean test
(T t).
●
It evaluates the predicate on the given argument.
– Non-abstract methods:
● and(other), or (other): returns a composed predicate that represents the
logical conjunction/disjunction of the instance and other.
● negate(): returns a predicate that represents the logical negation of the
instance.
●
…
– See:
[Link]
unction/[Link]
35
Built-in Functional Interfaces (5)
● [Link]<T>
– Represents a supplier of results.
– The functional method is T get().
●
It provides a result.
– Non-abstract methods: there are none
– See:
[Link]
[Link]/java/util/function/[Link]
36
Built-in Functional Interfaces (6)
● [Link]<T>
– Represents an operation that accepts a single input argument and
returns no result.
– The functional method is void accept
(T t).
●
It performs the operation on the given argument.
– Unlike most other functional interfaces, Consumer is expected to
operate via side-effects.
– Non-abstract methods:
● andThen (after): returns a composed Consumer that performs, in sequence,
the operation represented by the instance followed by the after operation.
– See:
[Link]
unction/[Link]
37
Lambda Expressions (1)
●
They represent an instance of an anonymous
inner class implementing a functional interface
in a very compact form.
– Evaluation of a lambda expression creates an
instance of an anonymous inner class implementing
a functional interface.
●
See: The Java Language Specification, Java
SE 21 Edition – Lambda Expressions
[Link]
ml/[Link]#jls-15.27
38
Lambda Expressions (2)
●
Consider the following instantiation:
new SomeFunctionalInterface() {
@Override
SomeType someMethod(parameters) {
body
}
}
●
The equivalent lambda expression is:
(parameters) -> {body}
– They consist of a list of formal parameters and a body.
39
Lambda Expressions (3)
●
A real-world example: creating and starting a
thread (pre-Java 8 and Java 8 style)
Runnable runnable = new Runnable() {
@Override
public void run() {
[Link]("Hello, World!");
}
};
Thread thread = new Thread(runnable);
[Link]();
40
Lambda Expressions (4)
●
Lambda expressions represent anonymous
functions.
41
Lambda Expressions (5)
●
Examples:
() -> {} // No parameters, result is void
() -> 42 // No parameters, expression body
() -> null // No parameters, expression body
() -> { return 42; } // No parameters, block body with return
() -> { [Link](); } // No parameters, void block body
() -> { // Complex block body with returns
if (true) return 12;
else {
int result = 15;
for (int i = 1; i < 10; i++)
result *= i;
return result;
}
} 42
Lambda Expressions (6)
●
Examples: (continued)
(int x) -> x + 1 // Single declared-type parameter
(int x) -> { return x + 1; } // Single declared-type parameter
(x) -> x + 1 // Single inferred-type parameter
x -> x + 1 // Parentheses are optional for
// single inferred-type parameter
44
Lambda Expressions (8)
●
Parameters: (continued)
– Each formal parameter of a lambda expression has
either an inferred type or a declared type.
●
It is a compile-time error if a lambda expression declares
a formal parameter with a declared type and a formal
parameter with an inferred type.
– Java SE 11 introduces the reserved type name var
for lambda parameters that allows the use of
annotations and modifiers on lambda parameters.
45
Lambda Expressions (9)
●
Body:
– A body is either a single expression or a block.
– Example:
file -> [Link]() && [Link]().endsWith(".java")
file -> {
return [Link]() && [Link]().endsWith(".java");
}
46
Lambda Expressions (10)
●
Body: (continued)
– Lambda expressions do not introduce a new level of scoping.
– The meaning of names and the this and super keywords
appearing in the body are the same as in the surrounding
context (except that lambda parameters introduce new
names).
– Local variables in the enclosing context can only be
referenced if they are final or effectively final, otherwise
a compile-time error occurs where the use is attempted.
● A variable is effectively final if it is never assigned to after its
initialization.
47
Lambda Expressions (11)
●
Body: (continued)
– Examples of (not) effectively final local variables:
void m1(int x) {
int y = 1;
foo(() -> x + y);
// Legal: x and y are both effectively final.
}
void m2(int x) {
foo(() -> x + 1);
x++;
// Illegal: x is not effectively final (it is incremented).
}
48
Lambda Expressions (12)
●
Lambda expression evaluation does not cause
the execution of the expression's body; instead,
this may occur at a later time when an
appropriate method of the functional interface is
invoked.
49
Lambda Expressions (13)
●
It is a compile-time error if a lambda expression
occurs in a program in someplace other than an
assignment context, an invocation context, or a
casting context.
– For example, the following are allowed occurrences
of the lambda expression x -> x + 1:
● IntFunction f = x -> x + 1;
● (IntFunction) x -> x + 1
● [Link](1, 2, 3).map(x -> x + 1)
50
Lambda Expressions (14)
●
Example:
– See:
[Link]
[Link]
[Link]/java/util/function/[Link]
IntBinaryOperator addition = (a, b) -> a + b;
IntBinaryOperator subtraction = (a, b) -> a – b;
[Link]([Link](40, 2)); // 42
[Link]([Link](10, 20));// -10
51
Method References (1)
●
A method reference expression is used to refer to the invocation
of a method without actually performing the invocation.
– Certain forms of method reference expression also allow class instance
creation or array creation to be treated as if it were a method invocation.
●
Evaluation of a method reference expression produces an
instance of a functional interface type.
– This does not cause the execution of the corresponding method;
instead, the execution may occur at a later time when an appropriate
method of the functional interface is invoked.
●
See: The Java Language Specification, Java SE 21 Edition –
Method Reference Expressions
[Link]
15.13
52
Method References (2)
●
It is a compile-time error if a method reference
expression occurs in a program in someplace
other than an assignment context, an invocation
context, or a casting context.
– For example, the following are allowed occurrences of
the method reference [Link]::println:
● Consumer<Object> consumer =
[Link]::println;
● (Consumer<Object>) [Link]::println
● [Link](1, 2).forEach([Link]::println)
53
Method References (3)
●
When more than one member method of a type has
the same name, or when a class has more than one
constructor, the appropriate method or constructor is
selected based on the functional interface type
targeted by the method reference expression.
●
If a method reference refers to an instance method,
then the implicit lambda expression has an extra
parameter compared to if it refers to a static method.
– The extra parameter specifies the instance on which the
method is invoked.
54
Method References (4)
●
Examples:
– Reference to a static method:
● System::currentTimeMillis
● [Link]::parse
● Collections::<String>singletonList
– Reference to an instance method of a particular
object:
● "Hello, World!"::length
● [Link]::println
55
Method References (5)
●
Examples: (continued)
– Reference to an instance method of an arbitrary object of a
particular type:
● String::length
● String::startsWith
● List::size
● List<String>::size
– Reference to a constructor:
● int[]::new
● ArrayList::new
● ArrayList<String>::new
56
Method References (6)
●
It is not possible to specify a particular
signature to be matched, for example,
Arrays::sort(int[]). Instead, the
functional interface provides argument types
that are used as input to the selection
algorithm.
57
Method References (7)
●
Examples:
– Reference to a static method:
● See: [Link]
[Link]
ase/java/util/function/[Link]
LongSupplier supplier = System::currentTimeMillis;
[Link]([Link]()); // 1549705345645
58
Method References (8)
●
Examples:
– Reference to an instance method of a particular
object:
● See: [Link]
[Link]
ase/java/util/function/[Link]
IntSupplier supplier = "Hello, World!"::length;
[Link]([Link]()); // 13
61
Method References (11)
●
Example:
– [Link]
[Link]
a/lang/[Link]
● (IntFunction<String>) Integer::toString
// static String toString
(int i);
● (BiFunction<Integer, Integer, String>)
Integer::toString
// static String toString
(int i, int radix);
● (Function<Integer, String>) Integer::toString //
error
– Ambiguous, matches both of these methods:
● String toString();
62
Method References (12)
●
Examples:
– Reference to a constructor:
IntFunction<int[]> intArrayFactory = int[]::new;
int[] a = [Link](5);
Supplier<ArrayList<String>> arrayListFactory1 =
ArrayList<String>::new; // default constructor
var list1 = [Link]();
[Link]("Hello, World!");
IntFunction<ArrayList<String>> arrayListFactory2 =
ArrayList<String>::new; // one-argument constructor
var list2 = [Link](5)
[Link]("Hello, World!");
63
Method References (13)
●
Practical example:
String[] fruits = new String[] {
"plum",
"PEACH",
"apricot",
"BANANA",
"APPLE",
"pear"
};
[Link](fruits, String::compareToIgnoreCase);
[Link]([Link](fruits));
// [APPLE, apricot, BANANA, PEACH, pear, plum]
64
Streams
●
A stream is a sequence of elements on which
operations can be performed.
● The [Link]<T>
interface represents a stream.
– See:
[Link]
[Link]/java/util/stream/[Link]
● See also the [Link] package.
[Link]
api/[Link]/java/util/stream/package-summar
[Link]
65
Streams: Differences from
Collections
●
Streams differ from collections in several ways:
– No storage: a stream is not a data structure that stores
elements; instead, it conveys elements from a source.
– Functional in nature: an operation on a stream produces a
result, but does not modify its source.
– Possibly unbounded: while collections have a finite size,
streams need not; short-circuiting operations can allow
computations on infinite streams to complete in finite time.
– Consumable: the elements of a stream are only visited
once during the life of a stream, just like those of an iterator.
66
Stream: Creation (1)
●
Streams can be obtained in a number of ways, for
example, from:
– Collections: via the stream() and parallelStream()
methods.
– Arrays: via the stream
(...) static factory methods of
[Link].
– Individual objects:
● Via the static factory methods of
(T t) and of
(T... values) of
[Link].
● Using a [Link] object returned by the builder()
method of [Link].
67
Stream: Creation (2)
●
Numerous Java SE classes provide methods for
obtaining streams, for example:
– [Link]: the lines() method
returns a stream of lines.
– [Link]: the lines() method returns a
stream of lines.
– …
● See: Uses of Interface [Link]
[Link]
[Link]/java/util/stream/class-use/[Link]
68
Stream: Creation (3)
●
Examples:
// Creating a stream from a collection:
import [Link];
70
Stream: Creation (5)
●
Examples: (continued)
// Creating a stream from a string:
Stream<String> stream = "Hello,\nWorld!".lines();
71
Stream: Creation (6)
●
Third-party libraries:
– jOOQ (license: non-free/Apache License 2.0) [Link]
[Link]
– Jakarta JSON Processing (JSON-P)
[Link]
[Link]
● Package [Link]
[Link]
[Link]
●
Implementations:
– Jakarta JSON Processing (license: Eclipse Public License v2/GPLv2)
[Link]
– Joy (license: Apache License 2.0) [Link] [Link]
– Speedment (license: non-free/Apache License 2.0)
[Link] [Link]
72
Streams: Operations (1)
●
Stream operations are divided into two types:
– Intermediate operations return a new stream.
● Examples: filter
(), map(), sorted()
– Terminal operations produce a non-stream result
or a side-effect.
● Thus, they have have a non-stream or void return type.
● Examples: count(), max(), forEach
()
73
Streams: Operations (2)
●
Intermediate operations are further divided into two
types:
– Stateless operations retain no state from previously seen
elements when processing a new element, thus, each
element can be processed independently of operations on
other elements.
● Examples: filter(), map()
– Stateful operations may incorporate state from previously
seen elements when processing new elements, they may
need to process the entire input before producing a result.
● Examples: distinct(), sorted()
74
Streams: Operations (3)
●
Some operations are labeled as short-circuiting
operations:
– An intermediate operation is short-circuiting if, when presented
with infinite input, it may produce a finite stream as a result.
● Examples: limit
(), takeWhile
()
– A terminal operation is short-circuiting if, when presented with
infinite input, it may terminate in finite time.
● Examples: anyMatch
(), findFirst()
●
Having a short-circuiting operation in the pipeline is a
necessary, but not sufficient, condition for the processing of
an infinite stream to terminate normally in finite time.
75
Streams: Order
●
Depending on the source and the intermediate operations
streams may or may not have a defined encounter order in
which elements are provided to the operations.
– Certain stream sources (such as [Link] or arrays) are
intrinsically ordered, whereas others (such as
[Link]) are not.
– Some intermediate operations, such as sorted(), may impose an
encounter order on an otherwise unordered stream, and others may
render an ordered stream unordered, such as
[Link]().
– Further, some terminal operations may ignore encounter order, such
as forEach() for parallel stream pipelines.
76
Streams: Desired Characteristics of
Behavioral Parameters (1)
●
Most stream operations accept parameters that
describe user-specified behavior.
●
These behavioral parameters are always
instances of a functional interface, and are
often lambda expressions or method
references.
77
Streams: Desired Characteristics of
Behavioral Parameters (2)
●
The desired characteristics of behavioral parameters:
– Non-interference:
●
Behavioral parameters should never modify the stream's data
source.
– Statelessness:
●
A stateful lambda expression (or other object implementing the
appropriate functional interface) is one whose result depends on
any state which might change during the execution of the stream
pipeline.
●
Stream pipeline results may be nondeterministic or incorrect if the
behavioral parameters to the stream operations are stateful.
78
Streams: Desired Characteristics of
Behavioral Parameters (3)
●
Example of violating the non-interference
requirement:
– The following code throws
[Link]
ion:
List<String> list = new ArrayList<>([Link]("apple", "banana"));
[Link]()
.peek(s -> [Link]("peach"))
.forEach([Link]::println); // throws exception
79
Streams: Desired Characteristics of
Behavioral Parameters (4)
●
Example of violating the statelessness
requirement:
for (int i = 0; i < 5; i++) {
Set<Integer> seen = new HashSet<>();
int sum = [Link](1, 2, 1, 3, 2, 1, 4)
.parallel()
.map(n -> [Link](n) ? n : 0)
.sum();
[Link](sum);
}
// 14
// 11
// 12
// 10
// 10 80
Streams: Desired Characteristics of
Behavioral Parameters (5)
●
Example of violating the statelessness
requirement: (continued)
– In order to always get the same (correct) result a
synchronized (thread-safe) set is required, however,
this undermines parallelism!
for (int i = 0; i < 5; i++) {
Set<Integer> seen = [Link](new HashSet<>());
int sum = [Link](1, 2, 1, 3, 2, 1, 4)
.parallel()
.map(n -> [Link](n) ? n : 0)
.sum();
[Link](sum);
}
// 10
// 10
// 10
// 10 81
// 10
Streams: Desired Characteristics of
Behavioral Parameters (6)
●
Example of violating the statelessness
requirement: (continued)
– Actually, the correct result can be obtained without
using any stateful lambda expression, taking full
advantage of parallelism.
for (int i = 0; i < 5; i++) {
int sum = [Link](1, 2, 1, 3, 2, 1, 4)
.parallel()
.distinct()
.sum();
[Link](sum);
}
// 10
// 10
// 10
// 10 82
// 10
Streams: Desired Characteristics of
Behavioral Parameters (7)
●
Behavioral parameters should not have any
side-effects.
– With the exception of terminal operations
forEach() and forEachOrdered(), side-effects
of behavioral parameters may not always be
executed.
●
A stream implementation is permitted to optimize the
computation by eliding operations (or entire stages) from
a stream pipeline – and therefore elide invocation of
behavioral parameters –, if it would not affect the result.
83
Streams: Desired Characteristics of
Behavioral Parameters (8)
●
Behavioral parameters should not have any
side-effects. (continued)
– Example: the count() terminal operation
●
An implementation may choose to not execute the
stream pipeline (either sequentially or in parallel) if it is
capable of computing the count directly from the stream
source.
– The map() and peek() intermediate operations are not
executed in the stream pipeline below!
[Link]("apple", "banana", "peach")
.stream()
.map(String::toUpperCase)
.peek([Link]::println)
84
.count(); // result: 3
Streams: Pipelines (1)
●
Stream operations can be chained to form a
pipeline.
●
A stream pipeline consists of a source followed
by zero or more intermediate operations, and a
terminal operation.
85
Streams: Pipelines (2)
●
Intermediate operations are always evaluated lazily.
– Executing an intermediate operation does not actually
perform any action.
– Traversal of the pipeline source does not begin until the
terminal operation of the pipeline is executed.
●
In almost all cases, terminal operations are
evaluated eagerly.
– Executing a terminal operation initiates the traversal of
the data source, the processing of the pipeline completes
before returning.
86
Streams: Pipelines (3)
●
Example:
[Link]("banana", "apple", "pear", "orange")
.filter(s -> [Link]() > 4) // intermediate operation
.map(String::toUpperCase) // intermediate operation
.sorted() // intermediate operation
.forEach([Link]::println);// terminal operation
// APPLE
// BANANA
// ORANGE
87
Streams: Pipelines (4)
●
After the terminal operation is performed, the
stream pipeline is considered consumed, and
can no longer be used.
– Example:
Stream<String> stream = [Link]("apple", "banana", "orange", "pear")
.filter(s -> [Link]() == 6);
[Link](s -> [Link]("e")); // result: true
[Link](); // result: IllegalStateException
88
Streams: Pipelines (5)
●
After the terminal operation is performed, the
stream pipeline is considered consumed, and
can no longer be used.
– If the same data source must be traversed again, a
new stream must be obtained from the data source.
●
Example:
Supplier<Stream<String>> supplier =
() -> [Link]("apple", "banana", "orange", "pear")
.filter(s -> [Link]() == 6);
[Link]().anyMatch(s -> [Link]("e")); // result: true
[Link]().count(); // result: 2
89
Streams: Pipelines (6)
●
The lazy evaluation of intermediate operations
offers opportunities for optimization.
– A chain of intermediate operations can be executed
in a single pass on the data, with minimal
intermediate state.
90
Streams: Pipelines (7)
●
Pipelines containing exclusively stateless
intermediate operations can be processed in a
single pass, whether sequential or parallel, with
minimal data buffering.
91
Streams: Primitive Streams (1)
●
The following specialized stream interfaces are
provided for processing sequences of values of
primitive types:
– [Link]
[Link]
[Link]/java/util/stream/[Link]
– [Link]
[Link]
[Link]/java/util/stream/[Link]
– [Link]
[Link]
[Link]/java/util/stream/[Link] 92
Streams: Primitive Streams (2)
●
Their operations accept instances of
specialized functional interfaces as parameters,
such as IntConsumer, IntFunction,
IntPredicate, or IntSupplier.
●
Compared to streams over objects, they
provide additional terminal operations, such as
average() and sum().
93
Streams: Primitive Streams (3)
●
Examples:
[Link](1, 2, 3, 4)
.map(i -> i * i)
.average()
.ifPresent([Link]::println); // 7.5
[Link](1, 5)
.map(i -> i * i)
.average()
.ifPresent([Link]::println); // 7.5
[Link](1, 3)
.mapToObj(i -> "x" + i)
.forEach([Link]::println);
// x1
// x2 94
// x3
Streams: Primitive Streams (4)
●
Examples: (continued)
[Link]("banana", "fig", "mango")
.mapToInt(String::length) // returns an IntStream
.forEach([Link]::println);
// 6
// 3
// 4
96
Streams: Pipeline Execution (2)
●
The vertical execution can reduce the number
of operations to be performed on the elements.
– Example:
[Link]("apple", "banana", "orange", "pear") // Output:
.map(s -> { map: apple
[Link]("map: " + s); anyMatch: APPLE
return [Link](); map: banana
}) anyMatch: BANANA
.anyMatch(s -> {
[Link]("anyMatch: " + s);
return [Link]("B");
});
97
Streams: Pipeline Execution (3)
●
Operation order may have significant impact on
performance!
– Example: compare the output with the output of the
next example!
[Link]("apple", "banana", "orange", "pear") // Output:
.map(s -> { map: apple
[Link]("map: " + s); filter: APPLE
return [Link](); forEach: APPLE
}) map: banana
.filter(s -> { filter: BANANA
[Link]("filter: " + s); map: orange
return [Link]("A"); filter: ORANGE
}) map: pear
.forEach(s -> [Link]("forEach: " + s)); filter: PEAR
98
Streams: Pipeline Execution (4)
●
Operation order may have significant impact on
performance!
– Example:
[Link]("apple", "banana", "orange", "pear") // Output:
.filter(s -> { filter: apple
[Link]("filter: " + s); map: apple
return [Link]("a"); forEach: APPLE
}) filter: banana
.map(s -> { filter: orange
[Link]("map: " + s); filter: pear
return [Link]();
})
.forEach(s -> [Link]("forEach: " + s));
99
Streams: Pipeline Execution (5)
●
Operation order may have significant impact on
performance!
– Example: sorted() is a stateful intermediate operation that may
consume the entire input before producing a result. Compare the
output with the output of the next example!
[Link]("pear", "banana", "apple", "orange") // Output:
.sorted((s1, s2) -> { sorted: banana, pear
[Link]("sorted: %s, %s\n", s1, s2); sorted: apple, banana
return [Link](s2); sorted: orange, apple
}) sorted: orange, banana
.filter(s -> { sorted: orange, pear
[Link]("filter: " + s); filter: apple
return [Link]("a"); map: apple
}) forEach: APPLE
.map(s -> { filter: banana
[Link]("map: " + s); filter: orange
return [Link](); filter: pear
})
.forEach(s -> [Link]("forEach: " + s)); 100
Streams: Pipeline Execution (6)
●
Operation order may have significant impact on
performance!
– Example: here, filter() reduces the stream to a
single element, and thus sorting does not happen at
all.
[Link]("pear", "banana", "apple", "orange") // Output:
.filter(s -> { filter: pear
[Link]("filter: " + s); filter: banana
return [Link]("a"); filter: apple
}) filter: orange
.sorted((s1, s2) -> { map: apple
[Link]("sorted: %s, %s\n", s1, s2); forEach: APPLE
return [Link](s2);
})
.map(s -> {
[Link]("map: " + s);
return [Link]();
}) 101
.forEach(s -> [Link]("forEach: " + s));
Streams: Reduction Operations (1)
●
A reduction operation (also called a fold) is a terminal
operation that takes a sequence of input elements
and combines them into a single summary result by
repeated application of a combining operation.
– Examples: finding the sum or maximum of a set of
numbers, or accumulating elements into a list.
●
The stream classes provide the general-purpose
reduction operations reduce() and collect(), and
they also have specialized reduction operations such
as sum(), max(), or count().
102
Streams: Reduction Operations (2)
●
Such operations can be readily implemented as
simple sequential loops, such as:
int sum = 0;
for (int x : numbers) {
sum += x;
}
103
Streams: Reduction Operations (3)
●
However, streams use a more abstract and
compact form to describe the computation that
enables parallelization.
– For example, the previous sequential loop can be
written as:
int sum = [Link]().reduce(0, (a, b) -> a + b);
or
104
Streams: Reduction Operations:
reduce (1)
● reduce() is a terminal operation that reduces
a sequence of elements to a single element.
105
Streams: Reduction Operations:
reduce (2)
●
Terminology:
– Identity: both an initial value for the reduction and a default
result if there are no input elements.
– Accumulator: a function that takes two parameters,
namely, a partial result of the reduction and the next
element, and produces a new partial result.
– Combiner: a function of two parameters that takes two
partial results and combines them into a new partial result.
●
The combiner is necessary in parallel reductions, where the input
is partitioned, a partial accumulation computed for each partition,
and then the partial results are combined to produce a final result.
106
Streams: Reduction Operations:
reduce (3)
●
Both the accumulator and the combiner must
must be an associative, non-interfering,
stateless function.
107
Stream: Reduction Operations:
reduce (4)
● The reduce() operation has the following three forms:
– reduce (accumulator):
[Link]
[Link]#reduce([Link])
● Performs a reduction using an accumulator (BinaryOperator).
– reduce (identity, accumulator):
[Link]
[Link]#reduce(T,[Link])
● Performs a reduction using an identity element and an accumulator (BinaryOperator).
– reduce (identity, accumulator, combiner):
[Link]
[Link]#reduce(U,[Link],[Link]
or)
● Performs a reduction using the provided identity element, accumulator (BiFunction),
and combiner (BinaryOperator).
●
Many reductions using this form can be represented more simply by an explicit
combination of map and reduce operations.
108
Streams: Reduction Operations:
reduce (5)
●
Examples:
[Link](1, 3, 1, 5, 2, 3)
.reduce((a, b) -> a + b)
.ifPresent([Link]::println); // 15
[Link](1, 3, 1, 5, 2, 3)
.reduce(Integer::sum)
.ifPresent([Link]::println); // 15
[Link](1, 3, 1, 5, 2, 3)
.reduce((a, b) -> a * b)
.ifPresent([Link]::println); // 90
[Link](1, 3, 1, 5, 2, 3)
.reduce(Integer::max)
.ifPresent([Link]::println); // 5 109
Stream: Reduction Operations:
reduce (6)
●
Examples: (continued)
[Link]("banana", "kiwi", "pineapple", "mango")
.reduce((x, y) -> [Link]() > [Link]() ? y : x)
.ifPresent([Link]::println); // pineapple
111
Stream: Reduction Operations:
reduce (8)
●
Performance issue:
– Typically, an accumulator function returns a new
value every time it processes an element of a
stream. This can have a significant impact on
performance!
– Example:
● Here, the concat() function creates a new String
object for each element of the stream.
Stream<String> strings;
113
Streams: Reduction Operations:
collect (2)
●
Terminology:
– Supplier: a function that creates a new mutable result container.
●
For a parallel execution, this function may be called multiple times and must return
a fresh value each time.
– Accumulator: a function that incorporates an element into a result
container.
– Combiner: a function that merges two partial result containers,
incorporating the elements from the second result container into the first
result container.
– Finisher: a function that performs a final transformation on a result
container.
– Collector: consists of a supplier, an accumulator, a combiner, and an
optional finisher.
114
Streams: Reduction Operations:
collect (3)
● The [Link]<T,A,R> interface
represents a mutable reduction operator.
[Link]
ava/util/stream/[Link]
– The class [Link] provides
implementations of many common mutable reductions via static
factory methods (e.g., counting(), groupingBy(...),
maxBy(...)).
[Link]
il/stream/[Link]
●
Some of these static factory methods obtain a collector argument called as a
downstream collector that is applied to the results of the returned collector.
●
A pipeline that contains one or more downstream collectors, is
called a multi-level reduction.
115
Streams: Reduction Operations:
collect (4)
●
Both the accumulator and the combiner must
must be an associative, non-interfering,
stateless function.
116
Streams: Reduction Operations:
collect (5)
● The collect() operation has the following two
forms:
– collect(supplier, accumulator, combiner):
[Link]
.base/java/util/stream/[Link]#collect([Link]
[Link],[Link],[Link]
[Link])
●
Performs a mutable reduction using the provided supplier
(Supplier), accumulator (BiConsumer), and combiner
(BiConsumer).
– collect(collector):
[Link]
.base/java/util/stream/[Link]#collect([Link]
[Link]) 117
●
Streams: Reduction Operations:
collect (6)
●
Example:
List<String> list = [Link](0, 10)
.mapToObj(Integer::toString)
.collect(ArrayList::new, // supplier
ArrayList::add, // accumulator
ArrayList::addAll // combiner
);
118
Streams: Reduction Operations:
collect (7)
●
Example:
String s = [Link]("banana", "kiwi", "pineapple", "mango")
.collect([Link](
() -> new StringJoiner(", ", "[", "]"), // supplier
StringJoiner::add, // accumulator
StringJoiner::merge, // combiner
StringJoiner::toString // finisher
)); // result: "[banana, kiwi, pineapple, mango]"
119
Streams: Reduction Operations:
collect (8)
●
Example:
List<String> list = [Link]("apple", "banana", "apple", "pear",
"apple", "apple", "banana");
Map<String, Long> distribution = [Link]()
.collect([Link]([Link](),
[Link]())
);
[Link](
(value, freq) -> [Link]("%s: %d\n", value, freq)
);
// banana: 2
// apple: 4
// pear: 1
120
Streams: Reduction Operations:
More Complex Examples (1)
●
Consider the following record class:
import [Link];
121
Streams: Reduction Operations:
More Complex Examples (2)
●
Suppose we also have the the following list:
List<LegoSet> legoSets = [Link](
new LegoSet("60073", [Link](2015), 233), // Service Truck
new LegoSet("60080", [Link](2015), 586), // Spaceport
new LegoSet("75211", [Link](2018), 519), // Imperial TIE Fighter
new LegoSet("21034", [Link](2017), 468) // London
);
122
Streams: Reduction Operations:
More Complex Examples (3)
●
Examples:
// Obtaining the list of big Lego sets with more than 500 pieces:
List<LegoSet> bigLegoSets = [Link]()
.filter(legoSet -> [Link]() > 500)
.collect([Link]());
[Link](bigLegoSets);
// [60080, 75211]
123
Streams: Reduction Operations:
More Complex Examples (4)
●
Examples:
// Total number of pieces:
int totalPieces = [Link]()
.collect([Link](LegoSet::pieces));
[Link](totalPieces); // 1806
124
Streams: Reduction Operations:
More Complex Examples (5)
●
Examples:
// Grouping Lego sets by year:
Map<Year, List<LegoSet>> legoSetsByYear = legoSets
.stream()
.collect([Link](LegoSet::year));
[Link](legoSetsByYear);
// {2017=[21034], 2018=[75211], 2015=[60073, 60080]}
126
Streams: Reduction Operations:
More Complex Examples (7)
●
Examples:
// Partitioning Lego sets by a predicate:
Map<Boolean, List<LegoSet>> map = [Link]()
.collect([Link](
legoSet -> [Link]() > 500));
[Link](map);
// {false=[60073, 21034], true=[60080, 75211]}
128
Streams: Parallelism (1)
●
All streams operations can be executed either sequentially
or in parallel.
●
The stream implementations in the JDK create serial
streams unless parallelism is explicitly requested.
– For example, the [Link] interface has
methods stream() and parallelStream(), which produce
sequential and parallel streams respectively.
●
Except for operations identified as explicitly
nondeterministic, such as findAny(), whether a stream
executes sequentially or in parallel should not change the
result of the computation.
129
Streams: Parallelism (2)
●
A parallel stream can be obtained by either of
the following methods:
– Calling the parallelStream() method of a
collection.
– Turning an already existing sequential stream into a
parallel one by calling the parallel() method.
130
Streams: Parallelism (3)
●
Parallel streams use a shared thread pool provided by
the ForkJoinPool class that can be obtained by calling
the [Link]() static method.
– See: [Link]
[Link]
va/util/concurrent/[Link]
– The size of the pool can be controlled via the
[Link]
lelism system property.
– The following line of code demonstrates how to obtain the size
of the pool:
[Link]("Pool size: " +
[Link]().getParallelism());
// Pool size: 7 131
Streams: Parallelism (4)
●
Example: a sequential stream
[Link]("apple", "banana", "orange", "pear")
.map(
s -> {
[Link]("map %-6s %s\n", s,
[Link]().getName());
return [Link]();
}
).forEach(
s -> [Link]("forEach %-6s %s\n", s,
[Link]().getName())
);
// map apple main
// forEach APPLE main
// map banana main
// forEach BANANA main
// map orange main
// forEach ORANGE main
// map pear main
// forEach PEAR main
132
Streams: Parallelism (5)
●
Example: parallel version of the previous
example
[Link]("apple", "banana", "orange", "pear")
.parallel()
.map(
s -> {
[Link]("map %-6s %s\n", s,
[Link]().getName());
return [Link]();
}
).forEach(
s -> [Link]("forEach %-6s %s\n", s,
[Link]().getName())
);
// map orange main
// map apple [Link]-worker-7
// forEach APPLE [Link]-worker-7
// map banana [Link]-worker-3
// map pear [Link]-worker-5
// forEach PEAR [Link]-worker-5
// forEach BANANA [Link]-worker-3 133
// forEach ORANGE main
Streams: Parallelism (6)
●
Example: evil numbers
– An evil number is a non-negative integer that has
an even number of 1s in its binary expansion.
●
See: [Link]
– Let's count evil numbers using a sequential and a
parallel stream and compare performance!
134
Streams: Parallelism (7)
●
Example: evil numbers (continued)
– Counting evil numbers using a sequential stream:
import [Link];
import [Link];
137
Streams: Parallelism (10)
●
Example: evil numbers (continued)
– Accumulating evil numbers into a list using a
sequential stream:
import [Link];
143
Infinite Streams (3)
●
Example: generating the next 5 evil numbers
skipping the first million of them
[Link](0, n -> n + 1)
.filter(n -> [Link](n) % 2 == 0)
.skip(1_000_000)
.limit(5)
.forEach([Link]::println);
// 2000001
// 2000002
// 2000004
// 2000007
// 2000008
144
New Features
● The [Link] interface is
extended with a default method toList() in
Java SE 16.
– Thus, [Link]() can be used instead of
[Link]([Link]()).
145
IDE Support
●
IntelliJ IDEA:
– Analyze Java Stream operations
[Link]
[Link]
– Replace stream API chain with loop
146
Stream Libraries
●
Guava (license: Apache License 2.0)
[Link]
– See the [Link] class.
●
Javadoc:
[Link]
ollect/[Link]
●
Mug (license: Apache License 2.0)
[Link]
– Javadoc: [Link]
●
StreamEx (license: Apache License 2.0)
[Link]
– Javadoc: [Link]
147
Streams: Conclusion (1)
●
Example:
– From: Brian Goetz. An introduction to the
[Link] library. May 9, 2016.
[Link]
brian-goetz/
148
Streams: Conclusion (2)
Set<Seller> sellers = new HashSet<>();
for (Txn t : txns) {
if ([Link]().getAge() >= 65)
[Link]([Link]());
}
List<Seller> sorted = new ArrayList<>(sellers);
[Link](sorted, new Comparator<Seller>() {
public int compare(Seller a, Seller b) {
return [Link]().compareTo([Link]());
}
});
for (Seller s : sorted)
[Link]([Link]());
[Link]()
.filter(t -> [Link]().getAge() >= 65)
.map(Txn::getSeller)
.distinct()
.sorted(comparing(Seller::getName))
.map(Seller::getName) 149
.forEach([Link]::println);
Further Recommended Reading (1)
●
Non-abstract interface methods:
– Brian Goetz. Interface evolution via virtual
extension methods. June 2011.
[Link]
nder%20Methods%[Link]
– The Java Tutorials – Trail: Learning the Java
Language – Lesson: Interfaces and Inheritance.
[Link]
150
Further Recommended Reading (2)
●
Streams:
– Brian Goetz. Java Streams – Explore the
[Link] library. 2016.
[Link]
– The Java Tutorials – Trail: Learning the Java
Language – Lesson: Aggregate Operations.
[Link]
reams/
151