0% found this document useful (0 votes)
7 views151 pages

Java Advanced Features: Interfaces & Streams

Uploaded by

Phoo Latt
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views151 pages

Java Advanced Features: Interfaces & Streams

Uploaded by

Phoo Latt
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Advanced Language Features of

Java

Péter Jeszenszky
Faculty of Informatics, University of Debrecen
[Link]@[Link]

Last modified: March 16, 2024


Topics Covered

Non-abstract interface methods
● [Link]

Functional interfaces

Lambda expressions

Streams

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();

default void forEach(Consumer<? super T> action) {


[Link](action);
for (T t : this) {
[Link](t);
}
}

default Spliterator<T> spliterator() {


return [Link](iterator(), 0);
}

} 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");
}
}

public class SomeClass implements A, B {


} // does not compile 13
Non-abstract Interface Methods:
Default Methods (6)

As an unintended consequence, default
methods enable multiple inheritance.
– Example: (continued)

The following error occurs when compiling the class
SomeClass:
[Link]: error: types A and B are incompatible;
public class SomeClass implements A, B {
^
class SomeClass inherits unrelated defaults for someMethod()
from types A and B
1 error

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];

public interface List<E> extends Collection<E> {

static <E> List<E> of() {


return (List<E>) ImmutableCollections.EMPTY_LIST;
}

static <E> List<E> of(E e1) {


return new ImmutableCollections.List12<>(e1);
}

} 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();

default List<Book> filterByPublisher(String publisher) {


return getBooks().stream()
.filter(book -> [Link]().equals(publisher))
.collect([Link]());
}

default List<Book> filterByKeyword(String keyword) {


return getBooks().stream()
.filter(book -> [Link]().contains(keyword))
.collect([Link]());
}
}

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();

default List<Book> filterByPublisher(String publisher) {


return filterBy(book -> [Link]().equals(publisher));
}

default List<Book> filterByKeyword(String keyword) {


return filterBy(book -> [Link]().contains(keyword));
}

private List<Book> filterBy(Predicate<Book> predicate) {


return getBooks().stream()
.filter(predicate)
.collect([Link]());
}
24
}
[Link] (1)

A container object which may or may not contain a non-null
value.

It is primarily intended for use as a method return type where
there is a clear need to represent “no result”, and where using
null is likely to cause errors.
– Forces the programmer to deal with the absence of a value, thus, it
helps to avoid NullPointerExceptions.
● A variable whose type is Optional should never itself be
null, it should always point to an Optional instance.
● See: [Link]<T>
[Link]
va/util/[Link]

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) {
// ...
}

Optional<Book> optional = findBook(isbn);


if ([Link]()) {
Book book = [Link]();
// work with Book object
} else {
// deal with missing object
}

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]();

Runnable runnable = () -> [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

(String s) -> [Link]() // Single declared-type parameter


(Thread t) -> { [Link](); } // Single declared-type parameter
s -> [Link]() // Single inferred-type parameter
t -> { [Link](); } // Single inferred-type parameter

(int x, int y) -> x + y // Multiple declared-type parameters


(x, y) -> x + y // Multiple inferred-type parameters
(var x, var y) -> x + y // Multiple inferred-type parameters
(x, int y) -> x + y // Illegal: can't mix inferred and 43
// declared types
Lambda Expressions (7)

Parameters:
– The formal parameters of a lambda expression, if any, are specified by
either a parenthesized list of comma-separated parameter specifiers
or a parenthesized list of comma-separated identifiers.

In a list of parameter specifiers, each parameter specifier consists of optional
modifiers, then a type (or var), then an identifier that specifies the name of the
parameter.

In a list of identifiers, each identifier specifies the name of the parameter.
– If a lambda expression has no formal parameters, then an empty pair
of parentheses appears before the -> and the body.
– If a lambda expression has exactly one formal parameter, and the
parameter is specified by an identifier instead of a parameter specifier,
then the parentheses around the identifier may be elided.

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

Function<String, LocalDate> parser = LocalDate::parse;


LocalDate localDate = [Link]("2019-02-09")

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

Consumer<Object> consumer = [Link]::println;


[Link]("Hello, World!"); // Hello, World!
[Link](42); // 42
[Link]([Link]()); // 2020-03-07
59
Method References (9)

Examples:
– Reference to an instance method of an arbitrary
object of a particular type:

See:
– [Link]
[Link]
a/util/function/[Link]
– [Link]
[Link]
a/util/function/[Link]

ToIntFunction<String> length = String::length;


[Link]([Link]("Hello, World!")); // 13

BiPredicate<String, String> startsWith = String::startsWith;


[Link]([Link]("Hello, World!", "He"));// true
60
Method References (10)

Example:
– [Link]
[Link]
[Link]/java/time/[Link]
● (Supplier<LocalDate>) LocalDate::now
// static LocalDate now();
● (Function<Clock, LocalDate>) LocalDate::now
// static LocalDate now​
(Clock clock);
● (Function<ZoneId, LocalDate>) LocalDate::now
// static LocalDate now​
(ZoneId zone);

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();

● static String toString​ (int i);

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];

List<String> list = [Link]("apple", "banana", "orange", "pear");


Stream<String> stream = [Link]();

// Creating a stream from an array:


import [Link];

Stream<String> stream = [Link](new String[] {


"apple",
"banana",
"orange",
"pear"
});
69
Stream: Creation (4)

Examples: (continued)
// Creating a stream from individual objects:
Stream<String> stream = [Link]("apple", "banana", "orange", "pear");

// Creating a stream from individual objects using a [Link]:


[Link]<String> builder = [Link]();
Stream<String> stream = [Link]("apple")
.add("banana")
.add("orange")
.add("pear")
.build();

70
Stream: Creation (5)

Examples: (continued)
// Creating a stream from a string:
Stream<String> stream = "Hello,\nWorld!".lines();

// Creating a stream from a text file:


import [Link];
import [Link];

Stream<String> stream = [Link]([Link]("[Link]"));

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

[Link]("A1", "B2", "C3", "D4")


.map(s -> [Link](1))
.mapToInt(Integer::parseInt) // returns an IntStream
.max()
.ifPresent([Link]::println); // 4

[Link](1.2, 2.3, 3.4, 4.5)


.mapToLong(Math::round) // returns an LongStream
95
.sum(); // result: 11
Streams: Pipeline Execution (1)

Pipelines are executed vertically, not
horizontally!
– Example:
[Link]("apple", "banana", "orange", "pear") // Output:
.filter(s -> { filter: apple
[Link]("filter: " + s); forEach: apple
return true; filter: banana
}) forEach: banana
.forEach(s -> [Link]("forEach: " + s)); filter: orange
forEach: orange
filter: pear
forEach: pear

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

int sum = [Link]().reduce(0, Integer::sum);

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

[Link]("banana", "kiwi", "pineapple", "mango")


.reduce(String::concat)
.ifPresent([Link]::println);
// bananakiwipineapplemango

[Link]("banana", "kiwi", "pineapple", "mango")


.reduce((x, y) -> x + "|" + y)
.ifPresent([Link]::println);
// banana|kiwi|pineapple|mango
110
Stream: Reduction Operations:
reduce (7)

Examples: (continued)
Integer sum1 = [Link](1, 3, 1, 5, 2, 3)
.reduce(0, (a, b) -> a + b); // result: 15

Integer sum2 = [Link](1, 3, 1, 5, 2, 3)


.reduce(0, Integer::sum); // result: 15

Integer prod = [Link](1, 3, 1, 5, 2, 3)


.reduce(1, (a, b) -> a * b); // result: 90

Integer max = [Link](1, 3, 1, 5, 2, 3)


.reduce(Integer.MIN_VALUE, Integer::max); // result: 5

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;

String concatenated = [Link]("", String::concat)


112
Streams: Reduction Operations:
collect (1)

A mutable reduction operation accumulates
input elements into a mutable result container,
such as a Collection or StringBuilder,
as it processes the elements in the stream.

The mutable reduction operation is called
collect().

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
);

that can be also written as:

List<String> list = [Link](0, 10)


.mapToObj(Integer::toString)
.collect([Link]());

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]"

that can be also written as:

String s = [Link]("banana", "kiwi", "pineapple", "mango")


.collect([Link](",", "[", "]"));
// 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];

public record LegoSet(String number, Year year, int pieces) {

public String toString() {


return number;
}

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

// Statistics about the number of pieces:


IntSummaryStatistics piecesSummary = [Link]()
.collect([Link](LegoSet::pieces));
[Link](piecesSummary);
// IntSummaryStatistics{count=4, sum=1806, min=233,
// average=451,500000, max=586}

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]}

// Counting Lego sets by year:


Map<Year, Long> numberOfLegoSetsByYear = legoSets
.stream()
.collect([Link](LegoSet::year,
[Link]())
);
[Link](numberOfLegoSetsByYear); 125
// {2017=1, 2018=1, 2015=2}
Streams: Reduction Operations:
More Complex Examples (6)

Example:
// Obtaining the Lego set with the most number of pieces:
[Link]()
.collect([Link](
[Link](LegoSet::pieces)))
.ifPresent([Link]::println);
// 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]}

// Partitioning Lego sets by a predicate and counting the


// Lego sets in each partition:
Map<Boolean, Long> map = [Link]()
.collect([Link](
legoSet -> [Link]() > 500,
[Link]()));
// {false=2, true=2} 127
Streams: Reduction Operations:
More Complex Examples (8)

Examples:
// The LEGO set with the most pieces by year:
[Link]()
.collect(groupingBy(LegoSet::year,
maxBy([Link](LegoSet::pieces))));
// {2017=Optional[21034], 2018=Optional[75211],
// 2015=Optional[60080]}

// The LEGO set with the most pieces by year:


[Link]().collect(groupingBy(LegoSet::year,
collectingAndThen(maxBy([Link](LegoSet::pieces)),
Optional::get)));
// {2017=21034, 2018=75211, 2015=60080}

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];

long startTime = [Link]();


long count = [Link](0, 1_000_000_000)
.filter(n -> [Link](n) % 2 == 0)
.count();
long endTime = [Link]();
[Link]("%d\nTime elapsed: %dms\n", count,
[Link](endTime – startTime));
// 500000000
// Time elapsed: 966ms
135
Streams: Parallelism (8)

Example: evil numbers (continued)
– Counting evil numbers using a sequential stream:
import [Link];
import [Link];

long startTime = [Link]();


long count = [Link](0, 1_000_000_000)
.parallel()
.filter(n -> [Link](n) % 2 == 0)
.count();
long endTime = [Link]();
[Link]("%d\nTime elapsed: %dms\n", count,
[Link](endTime – startTime));
// 500000000
// Time elapsed: 313ms 136
Streams: Parallelism (9)

Example: evil numbers (continued)
– The parallel stream pipeline outperforms the
sequential one when counting the numbers (313ms /
966ms = 0.32).
– Now, let's accumulate the numbers into a list instead
of counting them!

In this case, the sequential stream pipeline wins the
competition (1737ms / 5549ms = 0.31)!
● Moreover, running the parallel version also requires the -
Xmx4g command line option to be specified!

137
Streams: Parallelism (10)

Example: evil numbers (continued)
– Accumulating evil numbers into a list using a
sequential stream:
import [Link];

long startTime = [Link]();


List<Integer> result = [Link](0, 100_000_000)
.filter(n -> [Link](n) % 2 == 0)
.boxed()
.collect([Link]());
long endTime = [Link]();
[Link]("%d\nTime elapsed: %dms\n", [Link](),
[Link](endTime – startTime));
// 500000000
// Time elapsed: 1737ms
138
Streams: Parallelism (11)

Example: evil numbers (continued)
– Accumulating evil numbers into a list using a
parallel stream:
import [Link];

long startTime = [Link]();


List<Integer> result = [Link](0, 100_000_000)
.parallel()
.filter(n -> [Link](n) % 2 == 0)
.boxed()
.collect([Link]());
long endTime = [Link]();
[Link]("%d\nTime elapsed: %dms\n", [Link](),
[Link](endTime – startTime));
// 500000000
139
// Time elapsed: 5549ms
Streams: Parallelism (12)

Example: evil numbers (continued)
– Counting evil numbers using a traditional for loop
(runs faster than the sequential stream):
long count = 0;
long startTime = [Link]();
for (int i = 0; i < 1_000_000_000; i++) {
if ([Link](i) % 2 == 0) {
count++;
}
}
long endTime = [Link]();
[Link]("%d\nTime elapsed: %dms\n", count,
[Link](endTime – startTime));
// 500000000
// Time elapsed: 799ms
140
Streams: Parallelism (13)

Example: evil numbers (continued)
– Accumulating evil numbers into a list using a
traditional for loop (runs faster than streams):
ArrayList<Integer> result = new ArrayList<>();
long startTime = [Link]();
for (int i = 0; i < 100_000_000; i++) {
if ([Link](i) % 2 == 0) {
[Link](i);
}
}
long endTime = [Link]();
[Link]("%d\nTime elapsed: %dms\n", [Link](),
[Link](endTime – startTime));
// 500000000
// Time elapsed: 1515ms
141
Infinite Streams (1)

Example: generating a stream of random
numbers
– The doubles() static method of the the
[Link] class returns a stream of random
numbers.

See:
[Link]
ase/java/util/[Link]#doubles()
new Random().doubles()
.limit(5)
.forEach([Link]::println);
// 0.6236419197176003
// 0.12212206445812179
// 0.355836701340987
// 0.6088477599798222 142
// 0.4282955926878853
Infinite Streams (2)

Example: generating a stream of random
integers until a specific number is obtained
new Random().ints(0, 100)
.takeWhile(n -> n != 0)
.forEach([Link]::println)
// 49
// 53
// 6
// 48
// 80

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

You might also like