Modern Java A Guide To 8 Java8
User Manual:
Open the PDF directly: View PDF
.
Page Count: 90
| Download | |
| Open PDF In Browser | View PDF |
Modern Java - A Guide to Java 8
Table of Contents
Introduction
0
Modern Java - A Guide to Java 8
1
Java 8 Stream Tutorial
2
Java 8 Nashorn Tutorial
3
Java 8 Concurrency Tutorial: Threads and Executors
4
Java 8 Concurrency Tutorial: Synchronization and Locks
5
Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap
6
Java 8 API by Example: Strings, Numbers, Math and Files
7
Avoiding Null Checks in Java 8
8
Fixing Java 8 Stream Gotchas with IntelliJ IDEA
9
Using Backbone.js with Nashorn
10
2
Modern Java - A Guide to Java 8
Modern Java - A Guide to Java 8
Author: winterbe
From: java8-tutorial
License: MIT License
Introduction
3
Modern Java - A Guide to Java 8
Modern Java - A Guide to Java 8
“Java is still not dead—and people are starting to figure that out.”
Welcome to my introduction to Java 8. This tutorial guides you step by step through all new
language features. Backed by short and simple code samples you'll learn how to use default
interface methods, lambda expressions, method references and repeatable annotations. At
the end of the article you'll be familiar with the most recent API changes like streams,
functional interfaces, map extensions and the new Date API. No walls of text, just a bunch
of commented code snippets. Enjoy!
This article was originally posted on my blog. You should follow me on Twitter.
Table of Contents
Default Methods for Interfaces
Lambda expressions
Functional Interfaces
Method and Constructor References
Lambda Scopes
Accessing local variables
Accessing fields and static variables
Accessing Default Interface Methods
Built-in Functional Interfaces
Predicates
Functions
Suppliers
Consumers
Comparators
Optionals
Streams
Filter
Sorted
Map
Match
Count
Reduce
Parallel Streams
Sequential Sort
Modern Java - A Guide to Java 8
4
Modern Java - A Guide to Java 8
Parallel Sort
Maps
Date API
Clock
Timezones
LocalTime
LocalDate
LocalDateTime
Annotations
Where to go from here?
Default Methods for Interfaces
Java 8 enables us to add non-abstract method implementations to interfaces by utilizing the
default
keyword. This feature is also known as virtual extension methods.
Here is our first example:
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
Besides the abstract method
method
sqrt
calculate
the interface
Formula
also defines the default
. Concrete classes only have to implement the abstract method
The default method
sqrt
calculate
.
can be used out of the box.
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
formula.calculate(100);
formula.sqrt(16);
// 100.0
// 4.0
The formula is implemented as an anonymous object. The code is quite verbose: 6 lines of
code for such a simple calculation of
sqrt(a * 100)
. As we'll see in the next section, there's
a much nicer way of implementing single method objects in Java 8.
Lambda expressions
Modern Java - A Guide to Java 8
5
Modern Java - A Guide to Java 8
Let's start with a simple example of how to sort a list of strings in prior versions of Java:
List names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
The static utility method
Collections.sort
accepts a list and a comparator in order to sort
the elements of the given list. You often find yourself creating anonymous comparators and
pass them to the sort method.
Instead of creating anonymous objects all day long, Java 8 comes with a much shorter
syntax, lambda expressions:
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
As you can see the code is much shorter and easier to read. But it gets even shorter:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
For one line method bodies you can skip both the braces
{}
and the
return
keyword. But
it gets even shorter:
names.sort((a, b) -> b.compareTo(a));
List now has a
sort
method. Also the java compiler is aware of the parameter types so you
can skip them as well. Let's dive deeper into how lambda expressions can be used in the
wild.
Functional Interfaces
How does lambda expressions fit into Java's type system? Each lambda corresponds to a
given type, specified by an interface. A so called functional interface must contain exactly
one abstract method declaration. Each lambda expression of that type will be matched to
this abstract method. Since default methods are not abstract you're free to add default
methods to your functional interface.
Modern Java - A Guide to Java 8
6
Modern Java - A Guide to Java 8
We can use arbitrary interfaces as lambda expressions as long as the interface only
contains one abstract method. To ensure that your interface meet the requirements, you
should add the
@FunctionalInterface
annotation. The compiler is aware of this annotation
and throws a compiler error as soon as you try to add a second abstract method declaration
to the interface.
Example:
@FunctionalInterface
interface Converter {
T convert(F from);
}
Converter converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);
// 123
Keep in mind that the code is also valid if the
@FunctionalInterface
annotation would be
omitted.
Method and Constructor References
The above example code can be further simplified by utilizing static method references:
Converter converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted);
// 123
Java 8 enables you to pass references of methods or constructors via the
::
keyword. The
above example shows how to reference a static method. But we can also reference object
methods:
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
Something something = new Something();
Converter converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);
// "J"
Let's see how the
::
keyword works for constructors. First we define an example bean with
different constructors:
Modern Java - A Guide to Java 8
7
Modern Java - A Guide to Java 8
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
Next we specify a person factory interface to be used for creating new persons:
interface PersonFactory {
P create(String firstName, String lastName);
}
Instead of implementing the factory manually, we glue everything together via constructor
references:
PersonFactory personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");
We create a reference to the Person constructor via
Person::new
. The Java compiler
automatically chooses the right constructor by matching the signature of
PersonFactory.create
.
Lambda Scopes
Accessing outer scope variables from lambda expressions is very similar to anonymous
objects. You can access final variables from the local outer scope as well as instance fields
and static variables.
Accessing local variables
We can read final local variables from the outer scope of lambda expressions:
final int num = 1;
Converter stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2);
// 3
But different to anonymous objects the variable
num
does not have to be declared final.
This code is also valid:
Modern Java - A Guide to Java 8
8
Modern Java - A Guide to Java 8
int num = 1;
Converter stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2);
However
num
// 3
must be implicitly final for the code to compile. The following code does not
compile:
int num = 1;
Converter stringConverter =
(from) -> String.valueOf(from + num);
num = 3;
Writing to
num
from within the lambda expression is also prohibited.
Accessing fields and static variables
In contrast to local variables, we have both read and write access to instance fields and
static variables from within lambda expressions. This behaviour is well known from
anonymous objects.
class Lambda4 {
static int outerStaticNum;
int outerNum;
void testScopes() {
Converter stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};
Converter stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
}
}
Accessing Default Interface Methods
Remember the formula example from the first section? Interface
method
sqrt
Formula
defines a default
which can be accessed from each formula instance including anonymous
objects. This does not work with lambda expressions.
Default methods cannot be accessed from within lambda expressions. The following code
does not compile:
Formula formula = (a) -> sqrt(a * 100);
Modern Java - A Guide to Java 8
9
Modern Java - A Guide to Java 8
Built-in Functional Interfaces
The JDK 1.8 API contains many built-in functional interfaces. Some of them are well known
from older versions of Java like
Comparator
extended to enable Lambda support via the
or
Runnable
. Those existing interfaces are
@FunctionalInterface
annotation.
But the Java 8 API is also full of new functional interfaces to make your life easier. Some of
those new interfaces are well known from the Google Guava library. Even if you're familiar
with this library you should keep a close eye on how those interfaces are extended by some
useful method extensions.
Predicates
Predicates are boolean-valued functions of one argument. The interface contains various
default methods for composing predicates to complex logical terms (and, or, negate)
Predicate predicate = (s) -> s.length() > 0;
predicate.test("foo");
predicate.negate().test("foo");
// true
// false
Predicate nonNull = Objects::nonNull;
Predicate isNull = Objects::isNull;
Predicate isEmpty = String::isEmpty;
Predicate isNotEmpty = isEmpty.negate();
Functions
Functions accept one argument and produce a result. Default methods can be used to chain
multiple functions together (compose, andThen).
Function toInteger = Integer::valueOf;
Function backToString = toInteger.andThen(String::valueOf);
backToString.apply("123");
// "123"
Suppliers
Suppliers produce a result of a given generic type. Unlike Functions, Suppliers don't accept
arguments.
Supplier personSupplier = Person::new;
personSupplier.get();
// new Person
Consumers
Modern Java - A Guide to Java 8
10
Modern Java - A Guide to Java 8
Consumers represent operations to be performed on a single input argument.
Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
Comparators
Comparators are well known from older versions of Java. Java 8 adds various default
methods to the interface.
Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2);
comparator.reversed().compare(p1, p2);
// > 0
// < 0
Optionals
Optionals are not functional interfaces, but nifty utilities to prevent
NullPointerException
. It's
an important concept for the next section, so let's have a quick look at how Optionals work.
Optional is a simple container for a value which may be null or non-null. Think of a method
which may return a non-null result but sometimes return nothing. Instead of returning
you return an
Optional
null
in Java 8.
Optional optional = Optional.of("bam");
optional.isPresent();
optional.get();
optional.orElse("fallback");
// true
// "bam"
// "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0)));
// "b"
Streams
A
java.util.Stream
represents a sequence of elements on which one or more operations
can be performed. Stream operations are either intermediate or terminal. While terminal
operations return a result of a certain type, intermediate operations return the stream itself
so you can chain multiple method calls in a row. Streams are created on a source, e.g. a
java.util.Collection
like lists or sets (maps are not supported). Stream operations can
either be executed sequentially or parallely.
Streams are extremely powerful, so I wrote a separate Java 8 Streams Tutorial. You
should also check out Stream.js, a JavaScript port of the Java 8 Streams API.
Modern Java - A Guide to Java 8
11
Modern Java - A Guide to Java 8
Let's first look how sequential streams work. First we create a sample source in form of a list
of strings:
List stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");
Collections in Java 8 are extended so you can simply create streams either by calling
Collection.stream()
or
Collection.parallelStream()
. The following sections explain the
most common stream operations.
Filter
Filter accepts a predicate to filter all elements of the stream. This operation is intermediate
which enables us to call another stream operation (
forEach
) on the result. ForEach accepts
a consumer to be executed for each element in the filtered stream. ForEach is a terminal
operation. It's
, so we cannot call another stream operation.
void
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa2", "aaa1"
Sorted
Sorted is an intermediate operation which returns a sorted view of the stream. The elements
are sorted in natural order unless you pass a custom
Comparator
.
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa1", "aaa2"
Keep in mind that
sorted
does only create a sorted view of the stream without manipulating
the ordering of the backed collection. The ordering of
stringCollection
is untouched:
System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Modern Java - A Guide to Java 8
12
Modern Java - A Guide to Java 8
Map
The intermediate operation
map
converts each element into another object via the given
function. The following example converts each string into an upper-cased string. But you can
also use
map
to transform each object into another type. The generic type of the resulting
stream depends on the generic type of the function you pass to
map
.
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Match
Various matching operations can be used to check whether a certain predicate matches the
stream. All of those operations are terminal and return a boolean result.
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA);
// true
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA);
// false
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ);
// true
Count
Count is a terminal operation returning the number of elements in the stream as a
long
.
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB);
// 3
Reduce
Modern Java - A Guide to Java 8
13
Modern Java - A Guide to Java 8
This terminal operation performs a reduction on the elements of the stream with the given
function. The result is an
Optional
holding the reduced value.
Optional reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
Parallel Streams
As mentioned above streams can be either sequential or parallel. Operations on sequential
streams are performed on a single thread while operations on parallel streams are
performed concurrently on multiple threads.
The following example demonstrates how easy it is to increase the performance by using
parallel streams.
First we create a large list of unique elements:
int max = 1000000;
List values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
Now we measure the time it takes to sort a stream of this collection.
Sequential Sort
long t0 = System.nanoTime();
long count = values.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));
// sequential sort took: 899 ms
Parallel Sort
Modern Java - A Guide to Java 8
14
Modern Java - A Guide to Java 8
long t0 = System.nanoTime();
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));
// parallel sort took: 472 ms
As you can see both code snippets are almost identical but the parallel sort is roughly 50%
faster. All you have to do is change
stream()
to
parallelStream()
.
Maps
As already mentioned maps do not directly support streams. There's no
available on the
Map
stream()
method
interface itself, however you can create specialized streams upon the
keys, values or entries of a map via
map.entrySet().stream()
map.keySet().stream()
,
map.values().stream()
and
.
Furthermore maps support various new and useful methods for doing common tasks.
Map map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.putIfAbsent(i, "val" + i);
}
map.forEach((id, val) -> System.out.println(val));
The above code should be self-explaining:
if null checks;
forEach
putIfAbsent
prevents us from writing additional
accepts a consumer to perform operations for each value of the
map.
This example shows how to compute code on the map by utilizing functions:
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3);
// val33
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9);
// false
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23);
// true
map.computeIfAbsent(3, num -> "bam");
map.get(3);
// val33
Next, we learn how to remove entries for a given key, only if it's currently mapped to a given
value:
Modern Java - A Guide to Java 8
15
Modern Java - A Guide to Java 8
map.remove(3, "val3");
map.get(3);
// val33
map.remove(3, "val33");
map.get(3);
// null
Another helpful method:
map.getOrDefault(42, "not found");
// not found
Merging entries of a map is quite easy:
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9);
// val9
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9);
// val9concat
Merge either put the key/value into the map if no entry for the key exists, or the merging
function will be called to change the existing value.
Date API
Java 8 contains a brand new date and time API under the package
java.time
. The new
Date API is comparable with the Joda-Time library, however it's not the same. The following
examples cover the most important parts of this new API.
Clock
Clock provides access to the current date and time. Clocks are aware of a timezone and
may be used instead of
System.currentTimeMillis()
to retrieve the current time in
milliseconds since Unix EPOCH. Such an instantaneous point on the time-line is also
represented by the class
Instant
. Instants can be used to create legacy
java.util.Date
objects.
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant);
// legacy java.util.Date
Timezones
Modern Java - A Guide to Java 8
16
Modern Java - A Guide to Java 8
Timezones are represented by a
ZoneId
. They can easily be accessed via static factory
methods. Timezones define the offsets which are important to convert between instants and
local dates and times.
System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
LocalTime
LocalTime represents a time without a timezone, e.g. 10pm or 17:30:15. The following
example creates two local times for the timezones defined above. Then we compare both
times and calculate the difference in hours and minutes between both times.
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2));
// false
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hoursBetween);
System.out.println(minutesBetween);
// -3
// -239
LocalTime comes with various factory methods to simplify the creation of new instances,
including parsing of time strings.
LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late);
// 23:59:59
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.GERMAN);
LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime);
// 13:37
LocalDate
LocalDate represents a distinct date, e.g. 2014-03-11. It's immutable and works exactly
analog to LocalTime. The sample demonstrates how to calculate new dates by adding or
subtracting days, months or years. Keep in mind that each manipulation returns a new
instance.
Modern Java - A Guide to Java 8
17
Modern Java - A Guide to Java 8
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek);
// FRIDAY
Parsing a LocalDate from a string is just as simple as parsing a LocalTime:
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas);
// 2014-12-24
LocalDateTime
LocalDateTime represents a date-time. It combines date and time as seen in the above
sections into one instance.
LocalDateTime
is immutable and works similar to LocalTime and
LocalDate. We can utilize methods for retrieving certain fields from a date-time:
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek);
// WEDNESDAY
Month month = sylvester.getMonth();
System.out.println(month);
// DECEMBER
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay);
// 1439
With the additional information of a timezone it can be converted to an instant. Instants can
easily be converted to legacy dates of type
java.util.Date
.
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate);
// Wed Dec 31 23:59:59 CET 2014
Formatting date-times works just like formatting dates or times. Instead of using pre-defined
formats we can create formatters from custom patterns.
Modern Java - A Guide to Java 8
18
Modern Java - A Guide to Java 8
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy - HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string);
// Nov 03, 2014 - 07:13
Unlike
java.text.NumberFormat
the new
DateTimeFormatter
is immutable and thread-safe.
For details on the pattern syntax read here.
Annotations
Annotations in Java 8 are repeatable. Let's dive directly into an example to figure that out.
First, we define a wrapper annotation which holds an array of the actual annotations:
@interface Hints {
Hint[] value();
}
@Repeatable(Hints.class)
@interface Hint {
String value();
}
Java 8 enables us to use multiple annotations of the same type by declaring the annotation
@Repeatable
.
Variant 1: Using the container annotation (old school)
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}
Variant 2: Using repeatable annotations (new school)
@Hint("hint1")
@Hint("hint2")
class Person {}
Using variant 2 the java compiler implicitly sets up the
@Hints
annotation under the hood.
That's important for reading annotation information via reflection.
Modern Java - A Guide to Java 8
19
Modern Java - A Guide to Java 8
Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint);
// null
Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length); // 2
Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
System.out.println(hints2.length);
// 2
Although we never declared the
via
getAnnotation(Hints.class)
getAnnotationsByType
@Hints
annotation on the
Person
class, it's still readable
. However, the more convenient method is
which grants direct access to all annotated
@Hint
annotations.
Furthermore the usage of annotations in Java 8 is expanded to two new targets:
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
Where to go from here?
My programming guide to Java 8 ends here. If you want to learn more about all the new
classes and features of the JDK 8 API, check out my JDK8 API Explorer. It helps you
figuring out all the new classes and hidden gems of JDK 8, like
StampedLock
and
CompletableFuture
Arrays.parallelSort
,
- just to name a few.
I've also published a bunch of follow-up articles on my blog that might be interesting to you:
Java 8 Stream Tutorial
Java 8 Nashorn Tutorial
Java 8 Concurrency Tutorial: Threads and Executors
Java 8 Concurrency Tutorial: Synchronization and Locks
Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap
Java 8 API by Example: Strings, Numbers, Math and Files
Avoid Null Checks in Java 8
Fixing Java 8 Stream Gotchas with IntelliJ IDEA
Using Backbone.js with Java 8 Nashorn
You should follow me on Twitter. Thanks for reading!
Modern Java - A Guide to Java 8
20
Modern Java - A Guide to Java 8
Java 8 Stream Tutorial
July 31, 2014
This example-driven tutorial gives an in-depth overview about Java 8 streams. When I first
read about the
InputStream
Stream
and
API, I was confused about the name since it sounds similar to
OutputStream
from Java I/O. But Java 8 streams are a completely
different thing. Streams are Monads, thus playing a big part in bringing functional
programming to Java:
> In functional programming, a monad is a structure that represents computations defined as
sequences of steps. A type with a monad structure defines what it means to chain
operations, or nest functions of that type together.
This guide teaches you how to work with Java 8 streams and how to use the different kind of
available stream operations. You'll learn about the processing order and how the ordering of
stream operations affect runtime performance. The more powerful stream operations
reduce
,
collect
and
flatMap
are covered in detail. The tutorial ends with an in-depth
look at parallel streams.
If you're not yet familiar with Java 8 lambda expressions, functional interfaces and method
references, you probably want to read my Java 8 Tutorial first before starting with this
tutorial.
UPDATE - I'm currently working on a JavaScript implementation of the Java 8 Streams API
for the browser. If I've drawn your interest check out Stream.js on GitHub. Your Feedback is
highly appreciated.
How streams work
A stream represents a sequence of elements and supports different kind of operations to
perform computations upon those elements:
List myList =
Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList
.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
// C1
// C2
Java 8 Stream Tutorial
21
Modern Java - A Guide to Java 8
Stream operations are either intermediate or terminal. Intermediate operations return a
stream so we can chain multiple intermediate operations without using semicolons. Terminal
operations are either void or return a non-stream result. In the above example
map
and
sorted
are intermediate operations whereas
forEach
filter
,
is a terminal operation.
For a full list of all available stream operations see the Stream Javadoc. Such a chain of
stream operations as seen in the example above is also known as operation pipeline.
Most stream operations accept some kind of lambda expression parameter, a functional
interface specifying the exact behavior of the operation. Most of those operations must be
both non-interfering and stateless. What does that mean?
A function is non-interfering when it does not modify the underlying data source of the
stream, e.g. in the above example no lambda expression does modify
myList
by adding or
removing elements from the collection.
A function is stateless when the execution of the operation is deterministic, e.g. in the above
example no lambda expression depends on any mutable variables or states from the outer
scope which might change during execution.
Different kind of streams
Streams can be created from various data sources, especially collections. Lists and Sets
support new methods
stream()
and
parallelStream()
to either create a sequential or a
parallel stream. Parallel streams are capable of operating on multiple threads and will be
covered in a later section of this tutorial. We focus on sequential streams for now:
Arrays.asList("a1", "a2", "a3")
.stream()
.findFirst()
.ifPresent(System.out::println);
Calling the method
// a1
on a list of objects returns a regular object stream. But we
stream()
don't have to create collections in order to work with streams as we see in the next code
sample:
Stream.of("a1", "a2", "a3")
.findFirst()
.ifPresent(System.out::println);
Just use
Stream.of()
// a1
to create a stream from a bunch of object references.
Besides regular object streams Java 8 ships with special kinds of streams for working with
the primitive data types
IntStream
,
LongStream
Java 8 Stream Tutorial
int
and
,
long
and
double
DoubleStream
. As you might have guessed it's
.
22
Modern Java - A Guide to Java 8
IntStreams can replace the regular for-loop utilizing
IntStream.range()
:
IntStream.range(1, 4)
.forEach(System.out::println);
// 1
// 2
// 3
All those primitive streams work just like regular object streams with the following
differences: Primitive streams use specialized lambda expressions, e.g.
instead of
Function
or
IntPredicate
instead of
the additional terminal aggregate operations
Arrays.stream(new int[] {1, 2, 3})
.map(n -> 2 * n + 1)
.average()
.ifPresent(System.out::println);
Predicate
sum()
and
IntFunction
. And primitive streams support
average()
:
// 5.0
Sometimes it's useful to transform a regular object stream to a primitive stream or vice
versa. For that purpose object streams support the special mapping operations
mapToLong()
and
mapToDouble
mapToInt()
,
:
Stream.of("a1", "a2", "a3")
.map(s -> s.substring(1))
.mapToInt(Integer::parseInt)
.max()
.ifPresent(System.out::println);
// 3
Primitive streams can be transformed to object streams via
mapToObj()
:
IntStream.range(1, 4)
.mapToObj(i -> "a" + i)
.forEach(System.out::println);
// a1
// a2
// a3
Here's a combined example: the stream of doubles is first mapped to an int stream and than
mapped to an object stream of strings:
Stream.of(1.0, 2.0, 3.0)
.mapToInt(Double::intValue)
.mapToObj(i -> "a" + i)
.forEach(System.out::println);
// a1
// a2
// a3
Java 8 Stream Tutorial
23
Modern Java - A Guide to Java 8
Processing Order
Now that we've learned how to create and work with different kinds of streams, let's dive
deeper into how stream operations are processed under the hood.
An important characteristic of intermediate operations is laziness. Look at this sample where
a terminal operation is missing:
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> {
System.out.println("filter: " + s);
return true;
});
When executing this code snippet, nothing is printed to the console. That is because
intermediate operations will only be executed when a terminal operation is present.
Let's extend the above example by the terminal operation
forEach
:
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> {
System.out.println("filter: " + s);
return true;
})
.forEach(s -> System.out.println("forEach: " + s));
Executing this code snippet results in the desired output on the console:
filter:
forEach:
filter:
forEach:
filter:
forEach:
filter:
forEach:
filter:
forEach:
d2
d2
a2
a2
b1
b1
b3
b3
c
c
The order of the result might be surprising. A naive approach would be to execute the
operations horizontally one after another on all elements of the stream. But instead each
element moves along the chain vertically. The first string "d2" passes
forEach
filter
then
, only then the second string "a2" is processed.
This behavior can reduce the actual number of operations performed on each element, as
we see in the next example:
Java 8 Stream Tutorial
24
Modern Java - A Guide to Java 8
Stream.of("d2", "a2", "b1", "b3", "c")
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase();
})
.anyMatch(s -> {
System.out.println("anyMatch: " + s);
return s.startsWith("A");
});
//
//
//
//
map:
anyMatch:
map:
anyMatch:
The operation
d2
D2
a2
A2
anyMatch
returns
true
as soon as the predicate applies to the given input
element. This is true for the second element passed "A2". Due to the vertical execution of
the stream chain,
map
has only to be executed twice in this case. So instead of mapping all
elements of the stream,
map
will be called as few as possible.
Why order matters
The next example consists of two intermediate operations
terminal operation
forEach
map
and
filter
and the
. Let's once again inspect how those operations are being
executed:
Stream.of("d2", "a2", "b1", "b3", "c")
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase();
})
.filter(s -> {
System.out.println("filter: " + s);
return s.startsWith("A");
})
.forEach(s -> System.out.println("forEach: " + s));
//
//
//
//
//
//
//
//
//
//
//
map:
filter:
map:
filter:
forEach:
map:
filter:
map:
filter:
map:
filter:
d2
D2
a2
A2
A2
b1
B1
b3
B3
c
C
As you might have guessed both
the underlying collection whereas
map
and
forEach
filter
are called five times for every string in
is only called once.
We can greatly reduce the actual number of executions if we change the order of the
operations, moving
filter
Java 8 Stream Tutorial
to the beginning of the chain:
25
Modern Java - A Guide to Java 8
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> {
System.out.println("filter: " + s);
return s.startsWith("a");
})
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase();
})
.forEach(s -> System.out.println("forEach: " + s));
//
//
//
//
//
//
//
Now,
filter:
filter:
map:
forEach:
filter:
filter:
filter:
map
d2
a2
a2
A2
b1
b3
c
is only called once so the operation pipeline performs much faster for larger
numbers of input elements. Keep that in mind when composing complex method chains.
Let's extend the above example by an additional operation,
sorted
:
Stream.of("d2", "a2", "b1", "b3", "c")
.sorted((s1, s2) -> {
System.out.printf("sort: %s; %s\n", s1, s2);
return s1.compareTo(s2);
})
.filter(s -> {
System.out.println("filter: " + s);
return s.startsWith("a");
})
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase();
})
.forEach(s -> System.out.println("forEach: " + s));
Sorting is a special kind of intermediate operation. It's a so called stateful operation since in
order to sort a collection of elements you have to maintain state during ordering.
Executing this example results in the following console output:
sort:
sort:
sort:
sort:
sort:
sort:
sort:
sort:
filter:
map:
forEach:
filter:
filter:
filter:
filter:
a2; d2
b1; a2
b1; d2
b1; a2
b3; b1
b3; d2
c; b3
c; d2
a2
a2
A2
b1
b3
c
d2
Java 8 Stream Tutorial
26
Modern Java - A Guide to Java 8
First, the sort operation is executed on the entire input collection. In other words
executed horizontally. So in this case
sorted
sorted
is
is called eight times for multiple combinations
on every element in the input collection.
Once again we can optimize the performance by reordering the chain:
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> {
System.out.println("filter: " + s);
return s.startsWith("a");
})
.sorted((s1, s2) -> {
System.out.printf("sort: %s; %s\n", s1, s2);
return s1.compareTo(s2);
})
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase();
})
.forEach(s -> System.out.println("forEach: " + s));
//
//
//
//
//
//
//
filter:
filter:
filter:
filter:
filter:
map:
forEach:
d2
a2
b1
b3
c
a2
A2
In this example
sorted
is never been called because
filter
reduces the input collection
to just one element. So the performance is greatly increased for larger input collections.
Reusing Streams
Java 8 streams cannot be reused. As soon as you call any terminal operation the stream is
closed:
Stream stream =
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> s.startsWith("a"));
stream.anyMatch(s -> true);
stream.noneMatch(s -> true);
Calling
noneMatch
after
anyMatch
// ok
// exception
on the same stream results in the following exception:
java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
at java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459)
at com.winterbe.java8.Streams5.test7(Streams5.java:38)
at com.winterbe.java8.Streams5.main(Streams5.java:28)
Java 8 Stream Tutorial
27
Modern Java - A Guide to Java 8
To overcome this limitation we have to to create a new stream chain for every terminal
operation we want to execute, e.g. we could create a stream supplier to construct a new
stream with all intermediate operations already set up:
Supplier> streamSupplier =
() -> Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> s.startsWith("a"));
streamSupplier.get().anyMatch(s -> true);
streamSupplier.get().noneMatch(s -> true);
Each call to
get()
// ok
// ok
constructs a new stream on which we are save to call the desired
terminal operation.
Advanced Operations
Streams support plenty of different operations. We've already learned about the most
important operations like
filter
or
map
. I leave it up to you to discover all other available
operations (see Stream Javadoc). Instead let's dive deeper into the more complex
operations
collect
,
flatMap
and
reduce
.
Most code samples from this section use the following list of persons for demonstration
purposes:
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name;
}
}
List persons =
Arrays.asList(
new Person("Max", 18),
new Person("Peter", 23),
new Person("Pamela", 23),
new Person("David", 12));
Collect
Collect is an extremely useful terminal operation to transform the elements of the stream into
a different kind of result, e.g. a
List
,
Set
or
Map
. Collect accepts a
Collector
which
consists of four different operations: a supplier, an accumulator, a combiner and a finisher.
Java 8 Stream Tutorial
28
Modern Java - A Guide to Java 8
This sounds super complicated at first, but the good part is Java 8 supports various built-in
collectors via the
Collectors
class. So for the most common operations you don't have to
implement a collector yourself.
Let's start with a very common usecase:
List filtered =
persons
.stream()
.filter(p -> p.name.startsWith("P"))
.collect(Collectors.toList());
System.out.println(filtered);
// [Peter, Pamela]
As you can see it's very simple to construct a list from the elements of a stream. Need a set
instead of list - just use
Collectors.toSet()
.
The next example groups all persons by age:
Map> personsByAge = persons
.stream()
.collect(Collectors.groupingBy(p -> p.age));
personsByAge
.forEach((age, p) -> System.out.format("age %s: %s\n", age, p));
// age 18: [Max]
// age 23: [Peter, Pamela]
// age 12: [David]
Collectors are extremely versatile. You can also create aggregations on the elements of the
stream, e.g. determining the average age of all persons:
Double averageAge = persons
.stream()
.collect(Collectors.averagingInt(p -> p.age));
System.out.println(averageAge);
// 19.0
If you're interested in more comprehensive statistics, the summarizing collectors return a
special built-in summary statistics object. So we can simply determine min, max and
arithmetic average age of the persons as well as the sum and count.
IntSummaryStatistics ageSummary =
persons
.stream()
.collect(Collectors.summarizingInt(p -> p.age));
System.out.println(ageSummary);
// IntSummaryStatistics{count=4, sum=76, min=12, average=19.000000, max=23}
The next example joins all persons into a single string:
Java 8 Stream Tutorial
29
Modern Java - A Guide to Java 8
String phrase = persons
.stream()
.filter(p -> p.age >= 18)
.map(p -> p.name)
.collect(Collectors.joining(" and ", "In Germany ", " are of legal age."));
System.out.println(phrase);
// In Germany Max and Peter and Pamela are of legal age.
The join collector accepts a delimiter as well as an optional prefix and suffix.
In order to transform the stream elements into a map, we have to specify how both the keys
and the values should be mapped. Keep in mind that the mapped keys must be unique,
otherwise an
IllegalStateException
is thrown. You can optionally pass a merge function as
an additional parameter to bypass the exception:
Map map = persons
.stream()
.collect(Collectors.toMap(
p -> p.age,
p -> p.name,
(name1, name2) -> name1 + ";" + name2));
System.out.println(map);
// {18=Max, 23=Peter;Pamela, 12=David}
Now that we know some of the most powerful built-in collectors, let's try to build our own
special collector. We want to transform all persons of the stream into a single string
consisting of all names in upper letters separated by the
achieve this we create a new collector via
Collector.of()
|
pipe character. In order to
. We have to pass the four
ingredients of a collector: a supplier, an accumulator, a combiner and a finisher.
Collector personNameCollector =
Collector.of(
() -> new StringJoiner(" | "),
// supplier
(j, p) -> j.add(p.name.toUpperCase()), // accumulator
(j1, j2) -> j1.merge(j2),
// combiner
StringJoiner::toString);
// finisher
String names = persons
.stream()
.collect(personNameCollector);
System.out.println(names);
// MAX | PETER | PAMELA | DAVID
Since strings in Java are immutable, we need a helper class like
StringJoiner
to let the
collector construct our string. The supplier initially constructs such a StringJoiner with the
appropriate delimiter. The accumulator is used to add each persons upper-cased name to
the StringJoiner. The combiner knows how to merge two StringJoiners into one. In the last
step the finisher constructs the desired String from the StringJoiner.
FlatMap
Java 8 Stream Tutorial
30
Modern Java - A Guide to Java 8
We've already learned how to transform the objects of a stream into another type of objects
by utilizing the
map
operation. Map is kinda limited because every object can only be
mapped to exactly one other object. But what if we want to transform one object into multiple
others or none at all? This is where
flatMap
comes to the rescue.
FlatMap transforms each element of the stream into a stream of other objects. So each
object will be transformed into zero, one or multiple other objects backed by streams. The
contents of those streams will then be placed into the returned stream of the
flatMap
operation.
Before we see
flatMap
in action we need an appropriate type hierarchy:
class Foo {
String name;
List bars = new ArrayList<>();
Foo(String name) {
this.name = name;
}
}
class Bar {
String name;
Bar(String name) {
this.name = name;
}
}
Next, we utilize our knowledge about streams to instantiate a couple of objects:
List foos = new ArrayList<>();
// create foos
IntStream
.range(1, 4)
.forEach(i -> foos.add(new Foo("Foo" + i)));
// create bars
foos.forEach(f ->
IntStream
.range(1, 4)
.forEach(i -> f.bars.add(new Bar("Bar" + i + " <- " + f.name))));
Now we have a list of three foos each consisting of three bars.
FlatMap accepts a function which has to return a stream of objects. So in order to resolve
the bar objects of each foo, we just pass the appropriate function:
Java 8 Stream Tutorial
31
Modern Java - A Guide to Java 8
foos.stream()
.flatMap(f -> f.bars.stream())
.forEach(b -> System.out.println(b.name));
//
//
//
//
//
//
//
//
//
Bar1
Bar2
Bar3
Bar1
Bar2
Bar3
Bar1
Bar2
Bar3
<<<<<<<<<-
Foo1
Foo1
Foo1
Foo2
Foo2
Foo2
Foo3
Foo3
Foo3
As you can see, we've successfully transformed the stream of three foo objects into a
stream of nine bar objects.
Finally, the above code example can be simplified into a single pipeline of stream
operations:
IntStream.range(1, 4)
.mapToObj(i -> new Foo("Foo" + i))
.peek(f -> IntStream.range(1, 4)
.mapToObj(i -> new Bar("Bar" + i + " <- " f.name))
.forEach(f.bars::add))
.flatMap(f -> f.bars.stream())
.forEach(b -> System.out.println(b.name));
FlatMap is also available for the
Optional
class introduced in Java 8. Optionals
flatMap
operation returns an optional object of another type. So it can be utilized to prevent nasty
null
checks.
Think of a highly hierarchical structure like this:
class Outer {
Nested nested;
}
class Nested {
Inner inner;
}
class Inner {
String foo;
}
In order to resolve the inner string
foo
of an outer instance you have to add multiple null
checks to prevent possible NullPointerExceptions:
Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
System.out.println(outer.nested.inner.foo);
}
The same behavior can be obtained by utilizing optionals
Java 8 Stream Tutorial
flatMap
operation:
32
Modern Java - A Guide to Java 8
Optional.of(new Outer())
.flatMap(o -> Optional.ofNullable(o.nested))
.flatMap(n -> Optional.ofNullable(n.inner))
.flatMap(i -> Optional.ofNullable(i.foo))
.ifPresent(System.out::println);
Each call to
returns an
flatMap
Optional
wrapping the desired object if present or
null
if
absent.
Reduce
The reduction operation combines all elements of the stream into a single result. Java 8
supports three different kind of
reduce
methods. The first one reduces a stream of
elements to exactly one element of the stream. Let's see how we can use this method to
determine the oldest person:
persons
.stream()
.reduce((p1, p2) -> p1.age > p2.age ? p1 : p2)
.ifPresent(System.out::println);
// Pamela
The
reduce
BiFunction
are like
method accepts a
BinaryOperator
accumulator function. That's actually a
where both operands share the same type, in that case
Function
Person
. BiFunctions
but accept two arguments. The example function compares both persons
ages in order to return the person with the maximum age.
The second
reduce
method accepts both an identity value and a
BinaryOperator
accumulator. This method can be utilized to construct a new Person with the aggregated
names and ages from all other persons in the stream:
Person result =
persons
.stream()
.reduce(new Person("", 0), (p1, p2) -> {
p1.age += p2.age;
p1.name += p2.name;
return p1;
});
System.out.format("name=%s; age=%s", result.name, result.age);
// name=MaxPeterPamelaDavid; age=76
The third
reduce
method accepts three parameters: an identity value, a
accumulator and a combiner function of type
is not restricted to the
Person
BinaryOperator
BiFunction
. Since the identity values type
type, we can utilize this reduction to determine the sum of
ages from all persons:
Java 8 Stream Tutorial
33
Modern Java - A Guide to Java 8
Integer ageSum = persons
.stream()
.reduce(0, (sum, p) -> sum += p.age, (sum1, sum2) -> sum1 + sum2);
System.out.println(ageSum);
// 76
As you can see the result is 76, but what's happening exactly under the hood? Let's extend
the above code by some debug output:
Integer ageSum = persons
.stream()
.reduce(0,
(sum, p) -> {
System.out.format("accumulator: sum=%s; person=%s\n", sum, p);
return sum += p.age;
},
(sum1, sum2) -> {
System.out.format("combiner: sum1=%s; sum2=%s\n", sum1, sum2);
return sum1 + sum2;
});
//
//
//
//
accumulator:
accumulator:
accumulator:
accumulator:
sum=0; person=Max
sum=18; person=Peter
sum=41; person=Pamela
sum=64; person=David
As you can see the accumulator function does all the work. It first get called with the initial
identity value 0 and the first person Max. In the next three steps
sum
continually increases
by the age of the last steps person up to a total age of 76.
Wait wat? The combiner never gets called? Executing the same stream in parallel will lift the
secret:
Integer ageSum = persons
.parallelStream()
.reduce(0,
(sum, p) -> {
System.out.format("accumulator: sum=%s; person=%s\n", sum, p);
return sum += p.age;
},
(sum1, sum2) -> {
System.out.format("combiner: sum1=%s; sum2=%s\n", sum1, sum2);
return sum1 + sum2;
});
//
//
//
//
//
//
//
accumulator: sum=0; person=Pamela
accumulator: sum=0; person=David
accumulator: sum=0; person=Max
accumulator: sum=0; person=Peter
combiner: sum1=18; sum2=23
combiner: sum1=23; sum2=12
combiner: sum1=41; sum2=35
Executing this stream in parallel results in an entirely different execution behavior. Now the
combiner is actually called. Since the accumulator is called in parallel, the combiner is
needed to sum up the separate accumulated values.
Java 8 Stream Tutorial
34
Modern Java - A Guide to Java 8
Let's dive deeper into parallel streams in the next chapter.
Parallel Streams
Streams can be executed in parallel to increase runtime performance on large amount of
input elements. Parallel streams use a common
ForkJoinPool.commonPool()
ForkJoinPool
available via the static
method. The size of the underlying thread-pool uses up to five
threads - depending on the amount of available physical CPU cores:
ForkJoinPool commonPool = ForkJoinPool.commonPool();
System.out.println(commonPool.getParallelism());
// 3
On my machine the common pool is initialized with a parallelism of 3 per default. This value
can be decreased or increased by setting the following JVM parameter:
-Djava.util.concurrent.ForkJoinPool.common.parallelism=5
Collections support the method
parallelStream()
Alternatively you can call the intermediate method
to create a parallel stream of elements.
parallel()
on a given stream to convert
a sequential stream to a parallel counterpart.
In order to understate the parallel execution behavior of a parallel stream the next example
prints information about the current thread to
sout
:
Arrays.asList("a1", "a2", "b1", "c2", "c1")
.parallelStream()
.filter(s -> {
System.out.format("filter: %s [%s]\n",
s, Thread.currentThread().getName());
return true;
})
.map(s -> {
System.out.format("map: %s [%s]\n",
s, Thread.currentThread().getName());
return s.toUpperCase();
})
.forEach(s -> System.out.format("forEach: %s [%s]\n",
s, Thread.currentThread().getName()));
By investigating the debug output we should get a better understanding which threads are
actually used to execute the stream operations:
Java 8 Stream Tutorial
35
Modern Java - A Guide to Java 8
filter:
filter:
map:
filter:
map:
filter:
map:
forEach:
forEach:
map:
forEach:
filter:
map:
forEach:
forEach:
b1
a2
a2
c2
c2
c1
c1
C2
A2
b1
B1
a1
a1
A1
C1
[main]
[ForkJoinPool.commonPool-worker-1]
[ForkJoinPool.commonPool-worker-1]
[ForkJoinPool.commonPool-worker-3]
[ForkJoinPool.commonPool-worker-3]
[ForkJoinPool.commonPool-worker-2]
[ForkJoinPool.commonPool-worker-2]
[ForkJoinPool.commonPool-worker-3]
[ForkJoinPool.commonPool-worker-1]
[main]
[main]
[ForkJoinPool.commonPool-worker-3]
[ForkJoinPool.commonPool-worker-3]
[ForkJoinPool.commonPool-worker-3]
[ForkJoinPool.commonPool-worker-2]
As you can see the parallel stream utilizes all available threads from the common
ForkJoinPool
for executing the stream operations. The output may differ in consecutive
runs because the behavior which particular thread is actually used is non-deterministic.
Let's extend the example by an additional stream operation,
sort
:
Arrays.asList("a1", "a2", "b1", "c2", "c1")
.parallelStream()
.filter(s -> {
System.out.format("filter: %s [%s]\n",
s, Thread.currentThread().getName());
return true;
})
.map(s -> {
System.out.format("map: %s [%s]\n",
s, Thread.currentThread().getName());
return s.toUpperCase();
})
.sorted((s1, s2) -> {
System.out.format("sort: %s <> %s [%s]\n",
s1, s2, Thread.currentThread().getName());
return s1.compareTo(s2);
})
.forEach(s -> System.out.format("forEach: %s [%s]\n",
s, Thread.currentThread().getName()));
The result may look strange at first:
Java 8 Stream Tutorial
36
Modern Java - A Guide to Java 8
filter:
filter:
map:
filter:
map:
filter:
map:
filter:
map:
map:
sort:
sort:
sort:
sort:
sort:
sort:
forEach:
forEach:
forEach:
forEach:
forEach:
c2
c1
c1
a2
a2
b1
b1
a1
a1
c2
A2
B1
C2
C1
C1
C1
A1
C2
B1
A2
C1
It seems that
[ForkJoinPool.commonPool-worker-3]
[ForkJoinPool.commonPool-worker-2]
[ForkJoinPool.commonPool-worker-2]
[ForkJoinPool.commonPool-worker-1]
[ForkJoinPool.commonPool-worker-1]
[main]
[main]
[ForkJoinPool.commonPool-worker-2]
[ForkJoinPool.commonPool-worker-2]
[ForkJoinPool.commonPool-worker-3]
<> A1 [main]
<> A2 [main]
<> B1 [main]
<> C2 [main]
<> B1 [main]
<> C2 [main]
[ForkJoinPool.commonPool-worker-1]
[ForkJoinPool.commonPool-worker-3]
[main]
[ForkJoinPool.commonPool-worker-2]
[ForkJoinPool.commonPool-worker-1]
sort
is executed sequentially on the main thread only. Actually,
parallel stream uses the new Java 8 method
Arrays.parallelSort()
sort
on a
under the hood. As
stated in Javadoc this method decides on the length of the array if sorting will be performed
sequentially or in parallel:
> If the length of the specified array is less than the minimum granularity, then it is sorted
using the appropriate Arrays.sort method.
Coming back to the
reduce
example from the last section. We already found out that the
combiner function is only called in parallel but not in sequential streams. Let's see which
threads are actually involved:
List persons = Arrays.asList(
new Person("Max", 18),
new Person("Peter", 23),
new Person("Pamela", 23),
new Person("David", 12));
persons
.parallelStream()
.reduce(0,
(sum, p) -> {
System.out.format("accumulator: sum=%s; person=%s [%s]\n",
sum, p, Thread.currentThread().getName());
return sum += p.age;
},
(sum1, sum2) -> {
System.out.format("combiner: sum1=%s; sum2=%s [%s]\n",
sum1, sum2, Thread.currentThread().getName());
return sum1 + sum2;
});
The console output reveals that both the accumulator and the combiner functions are
executed in parallel on all available threads:
Java 8 Stream Tutorial
37
Modern Java - A Guide to Java 8
accumulator:
accumulator:
accumulator:
accumulator:
combiner:
combiner:
combiner:
sum=0; person=Pamela;
sum=0; person=Max;
sum=0; person=David;
sum=0; person=Peter;
sum1=18; sum2=23;
sum1=23; sum2=12;
sum1=41; sum2=35;
[main]
[ForkJoinPool.commonPool-worker-3]
[ForkJoinPool.commonPool-worker-2]
[ForkJoinPool.commonPool-worker-1]
[ForkJoinPool.commonPool-worker-1]
[ForkJoinPool.commonPool-worker-2]
[ForkJoinPool.commonPool-worker-2]
In summary, it can be stated that parallel streams can bring be a nice performance boost to
streams with a large amount of input elements. But keep in mind that some parallel stream
operations like
reduce
and
collect
need additional computations (combine operations)
which isn't needed when executed sequentially.
Furthermore we've learned that all parallel stream operations share the same JVM-wide
common
ForkJoinPool
. So you probably want to avoid implementing slow blocking stream
operations since that could potentially slow down other parts of your application which rely
heavily on parallel streams.
That's it
My programming guide to Java 8 streams ends here. If you're interested in learning more
about Java 8 streams, I recommend to you the Stream Javadoc package documentation. If
you want to learn more about the underlying mechanisms, you probably want to read Martin
Fowlers article about Collection Pipelines.
If you're interested in JavaScript as well, you may want to have a look at Stream.js - a
JavaScript implementation of the Java 8 Streams API. You may also wanna read my Java 8
Tutorial and my Java 8 Nashorn Tutorial.
Hopefully this tutorial was helpful to you and you've enjoyed reading it. The full source code
of the tutorial samples is hosted on GitHub. Feel free to fork the repository or send me your
feedback via Twitter.
Happy coding!
Java 8 Stream Tutorial
38
Modern Java - A Guide to Java 8
Java 8 Nashorn Tutorial
April 05, 2014
Learn all about the Nashorn Javascript Engine with easily understood code examples. The
Nashorn Javascript Engine is part of Java SE 8 and competes with other standalone
engines like Google V8 (the engine that powers Google Chrome and Node.js). Nashorn
extends Javas capabilities by running dynamic javascript code natively on the JVM.
In the next ~15 minutes you learn how to evaluate javascript on the JVM dynamically during
runtime. The most recent Nashorn language features are demonstrated with small code
examples. You learn how to call javascript functions from java code and vice versa. At the
end you're ready to integrate dynamic scripts in your daily java business.
UPDATE - I'm currently working on a JavaScript implementation of the Java 8 Streams API
for the browser. If I've drawn your interest check out Stream.js on GitHub. Your Feedback is
highly appreciated.
Using Nashorn
The Nashorn javascript engine can either be used programmatically from java programs or
by utilizing the command line tool
work with
jjs
jjs
, which is located in
$JAVA_HOME/bin
. If you plan to
you might want to put a symbolic link for simple access:
Java 8 Nashorn Tutorial
39
Modern Java - A Guide to Java 8
$ cd /usr/bin
$ ln -s $JAVA_HOME/bin/jjs jjs
$ jjs
jjs> print('Hello World');
This tutorial focuses on using nashorn from java code, so let's skip
jjs
for now. A simple
HelloWorld in java code looks like this:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("print('Hello World!');");
In order to evaluate javascript code from java, you first create a nashorn script engine by
utilizing the
javax.script
package already known from Rhino (Javas legacy js engine from
Mozilla).
Javascript code can either be evaluated directly by passing javascript code as a string as
shown above. Or you can pass a file reader pointing to your .js script file:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("script.js"));
Nashorn javascript is based on ECMAScript 5.1 but future versions of nashorn will include
support for ECMAScript 6:
> The current strategy for Nashorn is to follow the ECMAScript specification. When we
release with JDK 8 we will be aligned with ECMAScript 5.1. The follow up major release of
Nashorn will align with ECMAScript Edition 6.
Nashorn defines a lot of language and API extensions to the ECMAScript standard. But first
let's take a look at how the communication between java and javascript code works.
Invoking Javascript Functions from Java
Nashorn supports the invocation of javascript functions defined in your script files directly
from java code. You can pass java objects as function arguments and return data back from
the function to the calling java method.
The following javascript functions will later be called from the java side:
var fun1 = function(name) {
print('Hi there from Javascript, ' + name);
return "greetings from javascript";
};
var fun2 = function (object) {
print("JS Class Definition: " + Object.prototype.toString.call(object));
};
Java 8 Nashorn Tutorial
40
Modern Java - A Guide to Java 8
In order to call a function you first have to cast the script engine to
Invocable interface is implemented by the
a method
invokeFunction
NashornScriptEngine
Invocable
. The
implementation and defines
to call a javascript function for a given name.
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("script.js"));
Invocable invocable = (Invocable) engine;
Object result = invocable.invokeFunction("fun1", "Peter Parker");
System.out.println(result);
System.out.println(result.getClass());
// Hi there from Javascript, Peter Parker
// greetings from javascript
// class java.lang.String
Executing the code results in three lines written to the console. Calling the function
pipes the result to
System.out
print
, so we see the javascript message first.
Now let's call the second function by passing arbitrary java objects:
invocable.invokeFunction("fun2", new Date());
// [object java.util.Date]
invocable.invokeFunction("fun2", LocalDateTime.now());
// [object java.time.LocalDateTime]
invocable.invokeFunction("fun2", new Person());
// [object com.winterbe.java8.Person]
Java objects can be passed without loosing any type information on the javascript side.
Since the script runs natively on the JVM we can utilize the full power of the Java API or
external libraries on nashorn.
Invoking Java Methods from Javascript
Invoking java methods from javascript is quite easy. We first define a static java method:
static String fun1(String name) {
System.out.format("Hi there from Java, %s", name);
return "greetings from java";
}
Java classes can be referenced from javascript via the
Java.type
API extension. It's similar
to importing classes in java code. As soon as the java type is defined we naturally call the
static method
fun1()
and print the result to
sout
. Since the method is static, we don't
have to create an instance first.
Java 8 Nashorn Tutorial
41
Modern Java - A Guide to Java 8
var MyJavaClass = Java.type('my.package.MyJavaClass');
var result = MyJavaClass.fun1('John Doe');
print(result);
// Hi there from Java, John Doe
// greetings from java
How does Nashorn handle type conversion when calling java methods with native javascript
types? Let's find out with a simple example.
The following java method simply prints the actual class type of the method parameter:
static void fun2(Object object) {
System.out.println(object.getClass());
}
To understand how type conversations are handled under the hood, we call this method with
different javascript types:
MyJavaClass.fun2(123);
// class java.lang.Integer
MyJavaClass.fun2(49.99);
// class java.lang.Double
MyJavaClass.fun2(true);
// class java.lang.Boolean
MyJavaClass.fun2("hi there")
// class java.lang.String
MyJavaClass.fun2(new Number(23));
// class jdk.nashorn.internal.objects.NativeNumber
MyJavaClass.fun2(new Date());
// class jdk.nashorn.internal.objects.NativeDate
MyJavaClass.fun2(new RegExp());
// class jdk.nashorn.internal.objects.NativeRegExp
MyJavaClass.fun2({foo: 'bar'});
// class jdk.nashorn.internal.scripts.JO4
Primitive javascript types are converted to the appropriate java wrapper class. Instead native
javascript objects are represented by internal adapter classes. Please keep in mind that
classes from
jdk.nashorn.internal
are subject to change, so you shouldn't program against
those classes in client-code:
> Anything marked internal will likely change out from underneath you.
ScriptObjectMirror
Java 8 Nashorn Tutorial
42
Modern Java - A Guide to Java 8
When passing native javascript objects to java you can utilize the class
ScriptObjectMirror
which is actually a java representation of the underlying javascript object. ScriptObjectMirror
implements the map interface and resides inside the package
. Classes
jdk.nashorn.api
from this package are intended to be used in client-code.
The next sample changes the parameter type from
Object
to
ScriptObjectMirror
so we
can extract some infos from the passed javascript object:
static void fun3(ScriptObjectMirror mirror) {
System.out.println(mirror.getClassName() + ": " +
Arrays.toString(mirror.getOwnKeys(true)));
}
When passing an object hash to this method, the properties are accessible on the java side:
MyJavaClass.fun3({
foo: 'bar',
bar: 'foo'
});
// Object: [foo, bar]
We can also call member functions on javascript object from java. Let's first define a
javascript type Person with properties
firstName
and
lastName
and method
.
getFullName
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = function() {
return this.firstName + " " + this.lastName;
}
}
The javascript method
callMember()
getFullName
can be called on the ScriptObjectMirror via
.
static void fun4(ScriptObjectMirror person) {
System.out.println("Full Name is: " + person.callMember("getFullName"));
}
When passing a new person to the java method, we see the desired result on the console:
var person1 = new Person("Peter", "Parker");
MyJavaClass.fun4(person1);
// Full Name is: Peter Parker
Language Extensions
Java 8 Nashorn Tutorial
43
Modern Java - A Guide to Java 8
Nashorn defines various language and API extensions to the ECMAScript standard. Let's
head right into the most recent features:
Typed Arrays
Native javascript arrays are untyped. Nashorn enables you to use typed java arrays in
javascript:
var IntArray = Java.type("int[]");
var array = new IntArray(5);
array[0] = 5;
array[1] = 4;
array[2] = 3;
array[3] = 2;
array[4] = 1;
try {
array[5] = 23;
} catch (e) {
print(e.message);
}
array[0] = "17";
print(array[0]);
// Array index out of range: 5
// 17
array[0] = "wrong type";
print(array[0]); // 0
array[0] = "17.3";
print(array[0]); // 17
The
int[]
array behaves like a real java int array. But additionally Nashorn performs
implicit type conversions under the hood when we're trying to add non-integer values to the
array. Strings will be auto-converted to int which is quite handy.
Collections and For Each
Instead of messing around with arrays we can use any java collection. First define the java
type via
Java.type
, then create new instances on demand.
var ArrayList = Java.type('java.util.ArrayList');
var list = new ArrayList();
list.add('a');
list.add('b');
list.add('c');
for each (var el in list) print(el);
// a, b, c
In order to iterate over collections and arrays Nashorn introduces the
for each
statement. It
works just like the foreach loop in java.
Here's another collection foreach example, utilizing
Java 8 Nashorn Tutorial
HashMap
:
44
Modern Java - A Guide to Java 8
var map = new java.util.HashMap();
map.put('foo', 'val1');
map.put('bar', 'val2');
for each (var e in map.keySet()) print(e);
// foo, bar
for each (var e in map.values()) print(e);
// val1, val2
Lambda expressions and Streams
Everyone loves lambdas and streams - so does Nashorn! Although ECMAScript 5.1 lacks
the compact arrow syntax from the Java 8 lambda expressions, we can use function literals
where ever lambda expressions are accepted.
var list2 = new java.util.ArrayList();
list2.add("ddd2");
list2.add("aaa2");
list2.add("bbb1");
list2.add("aaa1");
list2.add("bbb3");
list2.add("ccc");
list2.add("bbb2");
list2.add("ddd1");
list2
.stream()
.filter(function(el) {
return el.startsWith("aaa");
})
.sorted()
.forEach(function(el) {
print(el);
});
// aaa1, aaa2
Extending classes
Java types can simply be extended with the
Java.extend
extension. As you can see in the
next example, you can even create multi-threaded code in your scripts:
var Runnable = Java.type('java.lang.Runnable');
var Printer = Java.extend(Runnable, {
run: function() {
print('printed from a separate thread');
}
});
var Thread = Java.type('java.lang.Thread');
new Thread(new Printer()).start();
new Thread(function() {
print('printed from another thread');
}).start();
// printed from a separate thread
// printed from another thread
Parameter overloading
Java 8 Nashorn Tutorial
45
Modern Java - A Guide to Java 8
Methods and functions can either be called with the point notation or with the square braces
notation.
var System = Java.type('java.lang.System');
System.out.println(10);
// 10
System.out["println"](11.0);
// 11.0
System.out["println(double)"](12);
// 12.0
Passing the optional parameter type
println(double)
when calling a method with
overloaded parameters determines the exact method to be called.
Java Beans
Instead of explicitly working with getters and setters you can just use simple property names
both for getting or setting values from a java bean.
var Date = Java.type('java.util.Date');
var date = new Date();
date.year += 1900;
print(date.year); // 2014
Function Literals
For simple one line functions we can skip the curly braces:
function sqr(x) x * x;
print(sqr(3));
// 9
Binding properties
Properties from two different objects can be bound together:
var o1 = {};
var o2 = { foo: 'bar'};
Object.bindProperties(o1, o2);
print(o1.foo);
o1.foo = 'BAM';
print(o2.foo);
// bar
// BAM
Trimming strings
I like my strings trimmed.
print("
hehe".trimLeft());
print("hehe
".trimRight() + "he");
// hehe
// hehehe
Whereis
Java 8 Nashorn Tutorial
46
Modern Java - A Guide to Java 8
In case you forget where you are:
print(__FILE__, __LINE__, __DIR__);
Import Scopes
Sometimes it's useful to import many java packages at once. We can use the class
JavaImporter
to be used in conjunction with the
with
statement. All class files from the
imported packages are accessible within the local scope of the
with
statement:
var imports = new JavaImporter(java.io, java.lang);
with (imports) {
var file = new File(__FILE__);
System.out.println(file.getAbsolutePath());
// /path/to/my/script.js
}
Convert arrays
Some packages like
JavaImporter
java.util
can be accessed directly without utilizing
Java.type
or
:
var list = new java.util.ArrayList();
list.add("s1");
list.add("s2");
list.add("s3");
This code converts the java list to a native javascript array:
var jsArray = Java.from(list);
print(jsArray);
print(Object.prototype.toString.call(jsArray));
// s1,s2,s3
// [object Array]
And the other way around:
var javaArray = Java.to([3, 5, 7, 11], "int[]");
Calling Super
Accessing overridden members in javascript is traditionally awkward because javas
super
keyword doesn't exist in ECMAScript. Luckily nashorn goes to the rescue.
First we define a super type in java code:
Java 8 Nashorn Tutorial
47
Modern Java - A Guide to Java 8
class SuperRunner implements Runnable {
@Override
public void run() {
System.out.println("super run");
}
}
Next we override
from javascript. Pay attention to the extended nashorn
SuperRunner
syntax when creating a new
Runner
instance: The syntax of overriding members is
borrowed from javas anonymous objects.
var SuperRunner = Java.type('com.winterbe.java8.SuperRunner');
var Runner = Java.extend(SuperRunner);
var runner = new Runner() {
run: function() {
Java.super(runner).run();
print('on my run');
}
}
runner.run();
// super run
// on my run
We call the overridden method
SuperRunner.run()
by utilizing the
Java.super
extension.
Loading scripts
Evaluating additional script files from javascript is quite easy. We can load both local or
remote scripts with the
load
function.
I'm using Underscore.js a lot for my web front-ends, so let's reuse Underscore in Nashorn:
load('http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js');
var odds = _.filter([1, 2, 3, 4, 5, 6], function (num) {
return num % 2 == 1;
});
print(odds);
// 1, 3, 5
The external script will be evaluated in the same javascript context, so we can access the
underscore variable directly. Keep in mind that loading scripts can potentially break your own
code when variable names are overlapping each other.
This problem can be bypassed by loading script files into a new global context:
loadWithNewGlobal('script.js');
Command-line scripts
Java 8 Nashorn Tutorial
48
Modern Java - A Guide to Java 8
If you're interested in writing command-line (shell) scripts with Java, give Nake a try. Nake is
a simplified Make for Java 8 Nashorn. You define tasks in a project-specific
run those tasks by typing
nake -- myTask
Nakefile
, then
into the command line. Tasks are written in
javascript and run in Nashorns scripting mode, so you can utilize the full power of your
terminal as well as the JDK8 API and any java library.
For Java Developers writing command-line scripts is easy as never before...
That's it
I hope this guide was helpful to you and you enjoyed our journey to the Nashorn Javascript
Engine. For further information about Nashorn read here, here and here. A guide to coding
shell scripts with Nashorn can be found here.
I recently published a follow up article about how to use Backbone.js models with the
Nashorn Javascript Engine. If you want to learn more about Java 8 feel free to read my Java
8 Tutorial and my Java 8 Stream Tutorial.
The runnable source code from this Nashorn tutorial is hosted on GitHub. Feel free to fork
the repository or send me your feedback via Twitter.
Keep on coding!
Java 8 Nashorn Tutorial
49
Modern Java - A Guide to Java 8
Java 8 Concurrency Tutorial: Threads and
Executors
April 07, 2015
Welcome to the first part of my Java 8 Concurrency tutorial. This guide teaches you
concurrent programming in Java 8 with easily understood code examples. It's the first part
out of a series of tutorials covering the Java Concurrency API. In the next 15 min you learn
how to execute code in parallel via threads, tasks and executor services.
Part 1: Threads and Executors
Part 2: Synchronization and Locks
Part 3: Atomic Variables and ConcurrentMap
The Concurrency API was first introduced with the release of Java 5 and then progressively
enhanced with every new Java release. The majority of concepts shown in this article also
work in older versions of Java. However my code samples focus on Java 8 and make heavy
use of lambda expressions and other new features. If you're not yet familiar with lambdas I
recommend reading my Java 8 Tutorial first.
Threads and Runnables
All modern operating systems support concurrency both via processes) and threads.
Processes are instances of programs which typically run independent to each other, e.g. if
you start a java program the operating system spawns a new process which runs in parallel
to other programs. Inside those processes we can utilize threads to execute code
concurrently, so we can make the most out of the available cores of the CPU.
Java supports Threads since JDK 1.0. Before starting a new thread you have to specify the
code to be executed by this thread, often called the task. This is done by implementing
Runnable
- a functional interface defining a single void no-args method
run()
as
demonstrated in the following example:
Runnable task = () -> {
String threadName = Thread.currentThread().getName();
System.out.println("Hello " + threadName);
};
task.run();
Thread thread = new Thread(task);
thread.start();
System.out.println("Done!");
Java 8 Concurrency Tutorial: Threads and Executors
50
Modern Java - A Guide to Java 8
Since
Runnable
is a functional interface we can utilize Java 8 lambda expressions to print
the current threads name to the console. First we execute the runnable directly on the main
thread before starting a new thread.
The result on the console might look like this:
Hello main
Hello Thread-0
Done!
Or that:
Hello main
Done!
Hello Thread-0
Due to concurrent execution we cannot predict if the runnable will be invoked before or after
printing 'done'. The order is non-deterministic, thus making concurrent programming a
complex task in larger applications.
Threads can be put to sleep for a certain duration. This is quite handy to simulate long
running tasks in the subsequent code samples of this article:
Runnable runnable = () -> {
try {
String name = Thread.currentThread().getName();
System.out.println("Foo " + name);
TimeUnit.SECONDS.sleep(1);
System.out.println("Bar " + name);
}
catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.start();
When you run the above code you'll notice the one second delay between the first and the
second print statement.
TimeUnit
is a useful enum for working with units of time.
Alternatively you can achieve the same by calling
Working with the
Thread
Thread.sleep(1000)
.
class can be very tedious and error-prone. Due to that reason the
Concurrency API has been introduced back in 2004 with the release of Java 5. The API is
located in package
java.util.concurrent
and contains many useful classes for handling
concurrent programming. Since that time the Concurrency API has been enhanced with
every new Java release and even Java 8 provides new classes and methods for dealing with
concurrency.
Java 8 Concurrency Tutorial: Threads and Executors
51
Modern Java - A Guide to Java 8
Now let's take a deeper look at one of the most important parts of the Concurrency API - the
executor services.
Executors
The Concurrency API introduces the concept of an
ExecutorService
as a higher level
replacement for working with threads directly. Executors are capable of running
asynchronous tasks and typically manage a pool of threads, so we don't have to create new
threads manually. All threads of the internal pool will be reused under the hood for revenant
tasks, so we can run as many concurrent tasks as we want throughout the life-cycle of our
application with a single executor service.
This is how the first thread-example looks like using executors:
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("Hello " + threadName);
});
// => Hello pool-1-thread-1
The class
Executors
provides convenient factory methods for creating different kinds of
executor services. In this sample we use an executor with a thread pool of size one.
The result looks similar to the above sample but when running the code you'll notice an
important difference: the java process never stops! Executors have to be stopped explicitly otherwise they keep listening for new tasks.
An
ExecutorService
provides two methods for that purpose:
running tasks to finish while
shutdownNow()
shutdown()
waits for currently
interrupts all running tasks and shut the
executor down immediately.
This is the preferred way how I typically shutdown executors:
try {
System.out.println("attempt to shutdown executor");
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
System.err.println("tasks interrupted");
}
finally {
if (!executor.isTerminated()) {
System.err.println("cancel non-finished tasks");
}
executor.shutdownNow();
System.out.println("shutdown finished");
}
Java 8 Concurrency Tutorial: Threads and Executors
52
Modern Java - A Guide to Java 8
The executor shuts down softly by waiting a certain amount of time for termination of
currently running tasks. After a maximum of five seconds the executor finally shuts down by
interrupting all running tasks.
Callables and Futures
In addition to
Runnable
executors support another kind of task named
are functional interfaces just like runnables but instead of being
void
Callable
. Callables
they return a value.
This lambda expression defines a callable returning an integer after sleeping for one second:
Callable task = () -> {
try {
TimeUnit.SECONDS.sleep(1);
return 123;
}
catch (InterruptedException e) {
throw new IllegalStateException("task interrupted", e);
}
};
Callables can be submitted to executor services just like runnables. But what about the
callables result? Since
doesn't wait until the task completes, the executor service
submit()
cannot return the result of the callable directly. Instead the executor returns a special result
of type
Future
which can be used to retrieve the actual result at a later point in time.
ExecutorService executor = Executors.newFixedThreadPool(1);
Future future = executor.submit(task);
System.out.println("future done? " + future.isDone());
Integer result = future.get();
System.out.println("future done? " + future.isDone());
System.out.print("result: " + result);
After submitting the callable to the executor we first check if the future has already been
finished execution via
isDone()
. I'm pretty sure this isn't the case since the above callable
sleeps for one second before returning the integer.
Calling the method
get()
blocks the current thread and waits until the callable completes
before returning the actual result
123
. Now the future is finally done and we see the
following result on the console:
future done? false
future done? true
result: 123
Futures are tightly coupled to the underlying executor service. Keep in mind that every nonterminated future will throw exceptions if you shutdown the executor:
Java 8 Concurrency Tutorial: Threads and Executors
53
Modern Java - A Guide to Java 8
executor.shutdownNow();
future.get();
You might have noticed that the creation of the executor slightly differs from the previous
example. We use
newFixedThreadPool(1)
pool of size one. This is equivalent to
to create an executor service backed by a thread-
newSingleThreadExecutor()
but we could later
increase the pool size by simply passing a value larger than one.
Timeouts
Any call to
future.get()
will block and wait until the underlying callable has been
terminated. In the worst case a callable runs forever - thus making your application
unresponsive. You can simply counteract those scenarios by passing a timeout:
ExecutorService executor = Executors.newFixedThreadPool(1);
Future future = executor.submit(() -> {
try {
TimeUnit.SECONDS.sleep(2);
return 123;
}
catch (InterruptedException e) {
throw new IllegalStateException("task interrupted", e);
}
});
future.get(1, TimeUnit.SECONDS);
Executing the above code results in a
TimeoutException
:
Exception in thread "main" java.util.concurrent.TimeoutException
at java.util.concurrent.FutureTask.get(FutureTask.java:205)
You might already have guessed why this exception is thrown: We specified a maximum wait
time of one second but the callable actually needs two seconds before returning the result.
InvokeAll
Executors support batch submitting of multiple callables at once via
invokeAll()
. This
method accepts a collection of callables and returns a list of futures.
Java 8 Concurrency Tutorial: Threads and Executors
54
Modern Java - A Guide to Java 8
ExecutorService executor = Executors.newWorkStealingPool();
List> callables = Arrays.asList(
() -> "task1",
() -> "task2",
() -> "task3");
executor.invokeAll(callables)
.stream()
.map(future -> {
try {
return future.get();
}
catch (Exception e) {
throw new IllegalStateException(e);
}
})
.forEach(System.out::println);
In this example we utilize Java 8 functional streams in order to process all futures returned
by the invocation of
invokeAll
. We first map each future to its return value and then print
each value to the console. If you're not yet familiar with streams read my Java 8 Stream
Tutorial.
InvokeAny
Another way of batch-submitting callables is the method
different to
invokeAll()
invokeAny()
which works slightly
. Instead of returning future objects this method blocks until the first
callable terminates and returns the result of that callable.
In order to test this behavior we use this helper method to simulate callables with different
durations. The method returns a callable that sleeps for a certain amount of time until
returning the given result:
Callable callable(String result, long sleepSeconds) {
return () -> {
TimeUnit.SECONDS.sleep(sleepSeconds);
return result;
};
}
We use this method to create a bunch of callables with different durations from one to three
seconds. Submitting those callables to an executor via
invokeAny()
returns the string result
of the fastest callable - in that case task2:
Java 8 Concurrency Tutorial: Threads and Executors
55
Modern Java - A Guide to Java 8
ExecutorService executor = Executors.newWorkStealingPool();
List> callables = Arrays.asList(
callable("task1", 2),
callable("task2", 1),
callable("task3", 3));
String result = executor.invokeAny(callables);
System.out.println(result);
// => task2
The above example uses yet another type of executor created via
newWorkStealingPool()
This factory method is part of Java 8 and returns an executor of type
ForkJoinPool
.
which
works slightly different than normal executors. Instead of using a fixed size thread-pool
ForkJoinPools are created for a given parallelism size which per default is the number of
available cores of the hosts CPU.
ForkJoinPools exist since Java 7 and will be covered in detail in a later tutorial of this series.
Let's finish this tutorial by taking a deeper look at scheduled executors.
Scheduled Executors
We've already learned how to submit and run tasks once on an executor. In order to
periodically run common tasks multiple times, we can utilize scheduled thread pools.
A
ScheduledExecutorService
is capable of scheduling tasks to run either periodically or once
after a certain amount of time has elapsed.
This code sample schedules a task to run after an initial delay of three seconds has passed:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime());
ScheduledFuture> future = executor.schedule(task, 3, TimeUnit.SECONDS);
TimeUnit.MILLISECONDS.sleep(1337);
long remainingDelay = future.getDelay(TimeUnit.MILLISECONDS);
System.out.printf("Remaining Delay: %sms", remainingDelay);
Scheduling a task produces a specialized future of type
to
Future
- provides the method
getDelay()
ScheduledFuture
which - in addition
to retrieve the remaining delay. After this
delay has elapsed the task will be executed concurrently.
In order to schedule tasks to be executed periodically, executors provide the two methods
scheduleAtFixedRate()
and
scheduleWithFixedDelay()
. The first method is capable of
executing tasks with a fixed time rate, e.g. once every second as demonstrated in this
example:
Java 8 Concurrency Tutorial: Threads and Executors
56
Modern Java - A Guide to Java 8
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime());
int initialDelay = 0;
int period = 1;
executor.scheduleAtFixedRate(task, initialDelay, period, TimeUnit.SECONDS);
Additionally this method accepts an initial delay which describes the leading wait time before
the task will be executed for the first time.
Please keep in mind that
scheduleAtFixedRate()
doesn't take into account the actual
duration of the task. So if you specify a period of one second but the task needs 2 seconds
to be executed then the thread pool will working to capacity very soon.
In that case you should consider using
scheduleWithFixedDelay()
instead. This method
works just like the counterpart described above. The difference is that the wait time period
applies between the end of a task and the start of the next task. For example:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("Scheduling: " + System.nanoTime());
}
catch (InterruptedException e) {
System.err.println("task interrupted");
}
};
executor.scheduleWithFixedDelay(task, 0, 1, TimeUnit.SECONDS);
This example schedules a task with a fixed delay of one second between the end of an
execution and the start of the next execution. The initial delay is zero and the tasks duration
is two seconds. So we end up with an execution interval of 0s, 3s, 6s, 9s and so on. As you
can see
scheduleWithFixedDelay()
is handy if you cannot predict the duration of the
scheduled tasks.
This was the first part out of a series of concurrency tutorials. I recommend practicing the
shown code samples by your own. You find all code samples from this article on GitHub, so
feel free to fork the repo and give me star.
I hope you've enjoyed this article. If you have any further questions send me your feedback
in the comments below or via Twitter.
Part 1: Threads and Executors
Part 2: Synchronization and Locks
Part 3: Atomic Variables and ConcurrentMap
Java 8 Concurrency Tutorial: Threads and Executors
57
Modern Java - A Guide to Java 8
Java 8 Concurrency Tutorial: Synchronization
and Locks
April 30, 2015
Welcome to the second part of my Java 8 Concurrency Tutorial out of a series of guides
teaching multi-threaded programming in Java 8 with easily understood code examples. In
the next 15 min you learn how to synchronize access to mutable shared variables via the
synchronized keyword, locks and semaphores.
Part 1: Threads and Executors
Part 2: Synchronization and Locks
Part 3: Atomic Variables and ConcurrentMap
The majority of concepts shown in this article also work in older versions of Java. However
the code samples focus on Java 8 and make heavy use of lambda expressions and new
concurrency features. If you're not yet familiar with lambdas I recommend reading my Java 8
Tutorial first.
For simplicity the code samples of this tutorial make use of the two helper methods
sleep(seconds)
and
stop(executor)
as defined here.
Synchronized
In the previous tutorial) we've learned how to execute code in parallel via executor services.
When writing such multi-threaded code you have to pay particular attention when accessing
shared mutable variables concurrently from multiple threads. Let's just say we want to
increment an integer which is accessible simultaneously from multiple threads.
We define a field
count
with a method
increment()
to increase count by one:
int count = 0;
void increment() {
count = count + 1;
}
When calling this method concurrently from multiple threads we're in serious trouble:
Java 8 Concurrency Tutorial: Synchronization and Locks
58
Modern Java - A Guide to Java 8
ExecutorService executor = Executors.newFixedThreadPool(2);
IntStream.range(0, 10000)
.forEach(i -> executor.submit(this::increment));
stop(executor);
System.out.println(count);
// 9965
Instead of seeing a constant result count of 10000 the actual result varies with every
execution of the above code. The reason is that we share a mutable variable upon different
threads without synchronizing the access to this variable which results in a race condition.
Three steps have to be performed in order to increment the number: (i) read the current
value, (ii) increase this value by one and (iii) write the new value to the variable. If two
threads perform these steps in parallel it's possible that both threads perform step 1
simultaneously thus reading the same current value. This results in lost writes so the actual
result is lower. In the above sample 35 increments got lost due to concurrent
unsynchronized access to count but you may see different results when executing the code
by yourself.
Luckily Java supports thread-synchronization since the early days via the
keyword. We can utilize
synchronized
synchronized
to fix the above race conditions when incrementing
the count:
synchronized void incrementSync() {
count = count + 1;
}
When using
incrementSync()
concurrently we get the desired result count of 10000. No
race conditions occur any longer and the result is stable with every execution of the code:
ExecutorService executor = Executors.newFixedThreadPool(2);
IntStream.range(0, 10000)
.forEach(i -> executor.submit(this::incrementSync));
stop(executor);
System.out.println(count);
The
synchronized
// 10000
keyword is also available as a block statement.
void incrementSync() {
synchronized (this) {
count = count + 1;
}
}
Java 8 Concurrency Tutorial: Synchronization and Locks
59
Modern Java - A Guide to Java 8
Internally Java uses a so called monitor also known as monitor lock or intrinsic lock in order
to manage synchronization. This monitor is bound to an object, e.g. when using
synchronized methods each method share the same monitor of the corresponding object.
All implicit monitors implement the reentrant characteristics. Reentrant means that locks are
bound to the current thread. A thread can safely acquire the same lock multiple times without
running into deadlocks (e.g. a synchronized method calls another synchronized method on
the same object).
Locks
Instead of using implicit locking via the
synchronized
supports various explicit locks specified by the
Lock
keyword the Concurrency API
interface. Locks support various
methods for finer grained lock control thus are more expressive than implicit monitors.
Multiple lock implementations are available in the standard JDK which will be demonstrated
in the following sections.
ReentrantLock
The class
ReentrantLock
is a mutual exclusion lock with the same basic behavior as the
implicit monitors accessed via the
synchronized
keyword but with extended capabilities. As
the name suggests this lock implements reentrant characteristics just as implicit monitors.
Let's see how the above sample looks like using
ReentrantLock
:
ReentrantLock lock = new ReentrantLock();
int count = 0;
void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
A lock is acquired via
into a
try/finally
lock()
and released via
unlock()
. It's important to wrap your code
block to ensure unlocking in case of exceptions. This method is thread-
safe just like the synchronized counterpart. If another thread has already acquired the lock
subsequent calls to
lock()
pause the current thread until the lock has been unlocked. Only
one thread can hold the lock at any given time.
Locks support various methods for fine grained control as seen in the next sample:
Java 8 Concurrency Tutorial: Synchronization and Locks
60
Modern Java - A Guide to Java 8
ExecutorService executor = Executors.newFixedThreadPool(2);
ReentrantLock lock = new ReentrantLock();
executor.submit(() -> {
lock.lock();
try {
sleep(1);
} finally {
lock.unlock();
}
});
executor.submit(() -> {
System.out.println("Locked: " + lock.isLocked());
System.out.println("Held by me: " + lock.isHeldByCurrentThread());
boolean locked = lock.tryLock();
System.out.println("Lock acquired: " + locked);
});
stop(executor);
While the first task holds the lock for one second the second task obtains different
information about the current state of the lock:
Locked: true
Held by me: false
Lock acquired: false
The method
tryLock()
as an alternative to
lock()
tries to acquire the lock without pausing
the current thread. The boolean result must be used to check if the lock has actually been
acquired before accessing any shared mutable variables.
ReadWriteLock
The interface
ReadWriteLock
specifies another type of lock maintaining a pair of locks for
read and write access. The idea behind read-write locks is that it's usually safe to read
mutable variables concurrently as long as nobody is writing to this variable. So the read-lock
can be held simultaneously by multiple threads as long as no threads hold the write-lock.
This can improve performance and throughput in case that reads are more frequent than
writes.
ExecutorService executor = Executors.newFixedThreadPool(2);
Map map = new HashMap<>();
ReadWriteLock lock = new ReentrantReadWriteLock();
executor.submit(() -> {
lock.writeLock().lock();
try {
sleep(1);
map.put("foo", "bar");
} finally {
lock.writeLock().unlock();
}
});
Java 8 Concurrency Tutorial: Synchronization and Locks
61
Modern Java - A Guide to Java 8
The above example first acquires a write-lock in order to put a new value to the map after
sleeping for one second. Before this task has finished two other tasks are being submitted
trying to read the entry from the map and sleep for one second:
Runnable readTask = () -> {
lock.readLock().lock();
try {
System.out.println(map.get("foo"));
sleep(1);
} finally {
lock.readLock().unlock();
}
};
executor.submit(readTask);
executor.submit(readTask);
stop(executor);
When you execute this code sample you'll notice that both read tasks have to wait the whole
second until the write task has finished. After the write lock has been released both read
tasks are executed in parallel and print the result simultaneously to the console. They don't
have to wait for each other to finish because read-locks can safely be acquired concurrently
as long as no write-lock is held by another thread.
StampedLock
Java 8 ships with a new kind of lock called
locks just like in the example above. In contrast to
StampedLock
which also support read and write
StampedLock
return a stamp represented by a
ReadWriteLock
long
the locking methods of a
value. You can use these stamps to
either release a lock or to check if the lock is still valid. Additionally stamped locks support
another lock mode called optimistic locking.
Let's rewrite the last example code to use
StampedLock
instead of
Java 8 Concurrency Tutorial: Synchronization and Locks
ReadWriteLock
:
62
Modern Java - A Guide to Java 8
ExecutorService executor = Executors.newFixedThreadPool(2);
Map map = new HashMap<>();
StampedLock lock = new StampedLock();
executor.submit(() -> {
long stamp = lock.writeLock();
try {
sleep(1);
map.put("foo", "bar");
} finally {
lock.unlockWrite(stamp);
}
});
Runnable readTask = () -> {
long stamp = lock.readLock();
try {
System.out.println(map.get("foo"));
sleep(1);
} finally {
lock.unlockRead(stamp);
}
};
executor.submit(readTask);
executor.submit(readTask);
stop(executor);
Obtaining a read or write lock via
readLock()
or
writeLock()
returns a stamp which is later
used for unlocking within the finally block. Keep in mind that stamped locks don't implement
reentrant characteristics. Each call to lock returns a new stamp and blocks if no lock is
available even if the same thread already holds a lock. So you have to pay particular
attention not to run into deadlocks.
Just like in the previous
ReadWriteLock
example both read tasks have to wait until the write
lock has been released. Then both read tasks print to the console simultaneously because
multiple reads doesn't block each other as long as no write-lock is held.
The next example demonstrates optimistic locking:
Java 8 Concurrency Tutorial: Synchronization and Locks
63
Modern Java - A Guide to Java 8
ExecutorService executor = Executors.newFixedThreadPool(2);
StampedLock lock = new StampedLock();
executor.submit(() -> {
long stamp = lock.tryOptimisticRead();
try {
System.out.println("Optimistic Lock Valid: " + lock.validate(stamp));
sleep(1);
System.out.println("Optimistic Lock Valid: " + lock.validate(stamp));
sleep(2);
System.out.println("Optimistic Lock Valid: " + lock.validate(stamp));
} finally {
lock.unlock(stamp);
}
});
executor.submit(() -> {
long stamp = lock.writeLock();
try {
System.out.println("Write Lock acquired");
sleep(2);
} finally {
lock.unlock(stamp);
System.out.println("Write done");
}
});
stop(executor);
An optimistic read lock is acquired by calling
tryOptimisticRead()
which always returns a
stamp without blocking the current thread, no matter if the lock is actually available. If there's
already a write lock active the returned stamp equals zero. You can always check if a stamp
is valid by calling
lock.validate(stamp)
.
Executing the above code results in the following output:
Optimistic
Write Lock
Optimistic
Write done
Optimistic
Lock Valid: true
acquired
Lock Valid: false
Lock Valid: false
The optimistic lock is valid right after acquiring the lock. In contrast to normal read locks an
optimistic lock doesn't prevent other threads to obtain a write lock instantaneously. After
sending the first thread to sleep for one second the second thread obtains a write lock
without waiting for the optimistic read lock to be released. From this point the optimistic read
lock is no longer valid. Even when the write lock is released the optimistic read locks stays
invalid.
So when working with optimistic locks you have to validate the lock every time after
accessing any shared mutable variable to make sure the read was still valid.
Sometimes it's useful to convert a read lock into a write lock without unlocking and locking
again.
StampedLock
provides the method
tryConvertToWriteLock()
for that purpose as seen
in the next sample:
Java 8 Concurrency Tutorial: Synchronization and Locks
64
Modern Java - A Guide to Java 8
ExecutorService executor = Executors.newFixedThreadPool(2);
StampedLock lock = new StampedLock();
executor.submit(() -> {
long stamp = lock.readLock();
try {
if (count == 0) {
stamp = lock.tryConvertToWriteLock(stamp);
if (stamp == 0L) {
System.out.println("Could not convert to write lock");
stamp = lock.writeLock();
}
count = 23;
}
System.out.println(count);
} finally {
lock.unlock(stamp);
}
});
stop(executor);
The task first obtains a read lock and prints the current value of field
But if the current value is zero we want to assign a new value of
23
count
to the console.
. We first have to
convert the read lock into a write lock to not break potential concurrent access by other
threads. Calling
tryConvertToWriteLock()
doesn't block but may return a zero stamp
indicating that no write lock is currently available. In that case we call
writeLock()
to block
the current thread until a write lock is available.
Semaphores
In addition to locks the Concurrency API also supports counting semaphores. Whereas locks
usually grant exclusive access to variables or resources, a semaphore is capable of
maintaining whole sets of permits. This is useful in different scenarios where you have to
limit the amount concurrent access to certain parts of your application.
Here's an example how to limit access to a long running task simulated by
Java 8 Concurrency Tutorial: Synchronization and Locks
sleep(5)
:
65
Modern Java - A Guide to Java 8
ExecutorService executor = Executors.newFixedThreadPool(10);
Semaphore semaphore = new Semaphore(5);
Runnable longRunningTask = () -> {
boolean permit = false;
try {
permit = semaphore.tryAcquire(1, TimeUnit.SECONDS);
if (permit) {
System.out.println("Semaphore acquired");
sleep(5);
} else {
System.out.println("Could not acquire semaphore");
}
} catch (InterruptedException e) {
throw new IllegalStateException(e);
} finally {
if (permit) {
semaphore.release();
}
}
}
IntStream.range(0, 10)
.forEach(i -> executor.submit(longRunningTask));
stop(executor);
The executor can potentially run 10 tasks concurrently but we use a semaphore of size 5,
thus limiting concurrent access to 5. It's important to use a
try/finally
block to properly
release the semaphore even in case of exceptions.
Executing the above code results in the following output:
Semaphore
Semaphore
Semaphore
Semaphore
Semaphore
Could not
Could not
Could not
Could not
Could not
acquired
acquired
acquired
acquired
acquired
acquire semaphore
acquire semaphore
acquire semaphore
acquire semaphore
acquire semaphore
The semaphores permits access to the actual long running operation simulated by
sleep(5)
up to a maximum of 5. Every subsequent call to
tryAcquire()
elapses the
maximum wait timeout of one second, resulting in the appropriate console output that no
semaphore could be acquired.
This was the second part out of a series of concurrency tutorials. More parts will be released
in the near future, so stay tuned. As usual you find all code samples from this article on
GitHub, so feel free to fork the repo and try it by your own.
I hope you've enjoyed this article. If you have any further questions send me your feedback
in the comments below. You should also follow me on Twitter for more dev-related stuff!
Java 8 Concurrency Tutorial: Synchronization and Locks
66
Modern Java - A Guide to Java 8
Part 1: Threads and Executors
Part 2: Synchronization and Locks
Part 3: Atomic Variables and ConcurrentMap
Java 8 Concurrency Tutorial: Synchronization and Locks
67
Modern Java - A Guide to Java 8
Java 8 Concurrency Tutorial: Atomic Variables
and ConcurrentMap
May 22, 2015
Welcome to the third part of my tutorial series about multi-threaded programming in Java 8.
This tutorial covers two important parts of the Concurrency API: Atomic Variables and
Concurrent Maps. Both have been greatly improved with the introduction of lambda
expressions and functional programming in the latest Java 8 release. All those new features
are described with a bunch of easily understood code samples. Enjoy!
Part 1: Threads and Executors
Part 2: Synchronization and Locks
Part 3: Atomic Variables and ConcurrentMap
For simplicity the code samples of this tutorial make use of the two helper methods
sleep(seconds)
and
stop(executor)
as defined here.
AtomicInteger
The package
java.concurrent.atomic
contains many useful classes to perform atomic
operations. An operation is atomic when you can safely perform the operation in parallel on
multiple threads without using the
synchronized
keyword or locks as shown in my previous
tutorial.
Internally, the atomic classes make heavy use of compare-and-swap (CAS), an atomic
instruction directly supported by most modern CPUs. Those instructions usually are much
faster than synchronizing via locks. So my advice is to prefer atomic classes over locks in
case you just have to change a single mutable variable concurrently.
Now let's pick one of the atomic classes for a few examples:
AtomicInteger
AtomicInteger atomicInt = new AtomicInteger(0);
ExecutorService executor = Executors.newFixedThreadPool(2);
IntStream.range(0, 1000)
.forEach(i -> executor.submit(atomicInt::incrementAndGet));
stop(executor);
System.out.println(atomicInt.get());
// => 1000
Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap
68
Modern Java - A Guide to Java 8
By using
AtomicInteger
as a replacement for
we're able to increment the number
Integer
concurrently in a thread-safe manor without synchronizing the access to the variable. The
method
incrementAndGet()
is an atomic operation so we can safely call this method from
multiple threads.
AtomicInteger supports various kinds of atomic operations. The method
updateAndGet()
accepts a lambda expression in order to perform arbitrary arithmetic operations upon the
integer:
AtomicInteger atomicInt = new AtomicInteger(0);
ExecutorService executor = Executors.newFixedThreadPool(2);
IntStream.range(0, 1000)
.forEach(i -> {
Runnable task = () ->
atomicInt.updateAndGet(n -> n + 2);
executor.submit(task);
});
stop(executor);
System.out.println(atomicInt.get());
The method
accumulateAndGet()
IntBinaryOperator
// => 2000
accepts another kind of lambda expression of type
. We use this method to sum up all values from 0 to 1000 concurrently in
the next sample:
AtomicInteger atomicInt = new AtomicInteger(0);
ExecutorService executor = Executors.newFixedThreadPool(2);
IntStream.range(0, 1000)
.forEach(i -> {
Runnable task = () ->
atomicInt.accumulateAndGet(i, (n, m) -> n + m);
executor.submit(task);
});
stop(executor);
System.out.println(atomicInt.get());
// => 499500
Other useful atomic classes are AtomicBoolean, AtomicLong and AtomicReference.
LongAdder
The class
LongAdder
as an alternative to
AtomicLong
can be used to consecutively add
values to a number.
Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap
69
Modern Java - A Guide to Java 8
ExecutorService executor = Executors.newFixedThreadPool(2);
IntStream.range(0, 1000)
.forEach(i -> executor.submit(adder::increment));
stop(executor);
System.out.println(adder.sumThenReset());
LongAdder provides methods
and
add()
// => 1000
increment()
just like the atomic number classes
and is also thread-safe. But instead of summing up a single result this class maintains a set
of variables internally to reduce contention over threads. The actual result can be retrieved
by calling
sum()
or
sumThenReset()
.
This class is usually preferable over atomic numbers when updates from multiple threads
are more common than reads. This is often the case when capturing statistical data, e.g. you
want to count the number of requests served on a web server. The drawback of
LongAdder
is higher memory consumption because a set of variables is held in-memory.
LongAccumulator
LongAccumulator is a more generalized version of LongAdder. Instead of performing simple
add operations the class
LongBinaryOperator
LongAccumulator
builds around a lambda expression of type
as demonstrated in this code sample:
LongBinaryOperator op = (x, y) -> 2 * x + y;
LongAccumulator accumulator = new LongAccumulator(op, 1L);
ExecutorService executor = Executors.newFixedThreadPool(2);
IntStream.range(0, 10)
.forEach(i -> executor.submit(() -> accumulator.accumulate(i)));
stop(executor);
System.out.println(accumulator.getThenReset());
We create a LongAccumulator with the function
every call to
accumulate(i)
// => 2539
2 * x + y
and an initial value of one. With
both the current result and the value
i
are passed as
parameters to the lambda expression.
A
LongAccumulator
just like
LongAdder
maintains a set of variables internally to reduce
contention over threads.
ConcurrentMap
Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap
70
Modern Java - A Guide to Java 8
The interface
ConcurrentMap
extends the map interface and defines one of the most useful
concurrent collection types. Java 8 introduces functional programming by adding new
methods to this interface.
In the next code snippets we use the following sample map to demonstrates those new
methods:
ConcurrentMap map = new ConcurrentHashMap<>();
map.put("foo", "bar");
map.put("han", "solo");
map.put("r2", "d2");
map.put("c3", "p0");
The method
forEach()
accepts a lambda expression of type
BiConsumer
with both the key
and value of the map passed as parameters. It can be used as a replacement to for-each
loops to iterate over the entries of the concurrent map. The iteration is performed
sequentially on the current thread.
map.forEach((key, value) -> System.out.printf("%s = %s\n", key, value));
The method
given key. At least for the
just like
puts a new value into the map only if no value exists for the
putIfAbsent()
put()
ConcurrentHashMap
implementation of this method is thread-safe
so you don't have to synchronize when accessing the map concurrently from
different threads:
String value = map.putIfAbsent("c3", "p1");
System.out.println(value);
// p0
The method
getOrDefault()
returns the value for the given key. In case no entry exists for
this key the passed default value is returned:
String value = map.getOrDefault("hi", "there");
System.out.println(value);
// there
The method
replaceAll()
accepts a lambda expression of type
BiFunction
. BiFunctions
take two parameters and return a single value. In this case the function is called with the key
and the value of each map entry and returns a new value to be assigned for the current key:
map.replaceAll((key, value) -> "r2".equals(key) ? "d3" : value);
System.out.println(map.get("r2"));
// d3
Instead of replacing all values of the map
compute()
let's us transform a single entry. The
method accepts both the key to be computed and a bi-function to specify the transformation
of the value.
Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap
71
Modern Java - A Guide to Java 8
map.compute("foo", (key, value) -> value + value);
System.out.println(map.get("foo"));
// barbar
In addition to
compute()
two variants exist:
computeIfAbsent()
and
computeIfPresent()
.
The functional parameters of these methods only get called if the key is absent or present
respectively.
Finally, the method
merge()
can be utilized to unify a new value with an existing value in
the map. Merge accepts a key, the new value to be merged into the existing entry and a bifunction to specify the merging behavior of both values:
map.merge("foo", "boo", (oldVal, newVal) -> newVal + " was " + oldVal);
System.out.println(map.get("foo"));
// boo was foo
ConcurrentHashMap
All those methods above are part of the
ConcurrentMap
interface, thereby available to all
implementations of that interface. In addition the most important implementation
ConcurrentHashMap
has been further enhanced with a couple of new methods to perform
parallel operations upon the map.
Just like parallel streams those methods use a special
ForkJoinPool.commonPool()
ForkJoinPool
available via
in Java 8. This pool uses a preset parallelism which depends on
the number of available cores. Four CPU cores are available on my machine which results in
a parallelism of three:
System.out.println(ForkJoinPool.getCommonPoolParallelism());
// 3
This value can be decreased or increased by setting the following JVM parameter:
-Djava.util.concurrent.ForkJoinPool.common.parallelism=5
We use the same example map for demonstrating purposes but this time we work upon the
concrete implementation
ConcurrentHashMap
instead of the interface
ConcurrentMap
, so we
can access all public methods from this class:
ConcurrentHashMap map = new ConcurrentHashMap<>();
map.put("foo", "bar");
map.put("han", "solo");
map.put("r2", "d2");
map.put("c3", "p0");
Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap
72
Modern Java - A Guide to Java 8
Java 8 introduces three kinds of parallel operations:
forEach
,
search
and
reduce
. Each
of those operations are available in four forms accepting functions with keys, values, entries
and key-value pair arguments.
All of those methods use a common first argument called
parallelismThreshold
. This
threshold indicates the minimum collection size when the operation should be executed in
parallel. E.g. if you pass a threshold of 500 and the actual size of the map is 499 the
operation will be performed sequentially on a single thread. In the next examples we use a
threshold of one to always force parallel execution for demonstrating purposes.
ForEach
The method
is capable of iterating over the key-value pairs of the map in
forEach()
parallel. The lambda expression of type
BiConsumer
is called with the key and value of the
current iteration step. In order to visualize parallel execution we print the current threads
name to the console. Keep in mind that in my case the underlying
ForkJoinPool
uses up to
a maximum of three threads.
map.forEach(1, (key, value) ->
System.out.printf("key: %s; value: %s; thread: %s\n",
key, value, Thread.currentThread().getName()));
//
//
//
//
key:
key:
key:
key:
r2; value: d2; thread: main
foo; value: bar; thread: ForkJoinPool.commonPool-worker-1
han; value: solo; thread: ForkJoinPool.commonPool-worker-2
c3; value: p0; thread: main
Search
The method
search()
accepts a
current key-value pair or
null
BiFunction
returning a non-null search result for the
if the current iteration doesn't match the desired search
criteria. As soon as a non-null result is returned further processing is suppressed. Keep in
mind that
ConcurrentHashMap
is unordered. The search function should not depend on the
actual processing order of the map. If multiple entries of the map match the given search
function the result may be non-deterministic.
String result = map.search(1, (key, value) -> {
System.out.println(Thread.currentThread().getName());
if ("foo".equals(key)) {
return value;
}
return null;
});
System.out.println("Result: " + result);
//
//
//
//
ForkJoinPool.commonPool-worker-2
main
ForkJoinPool.commonPool-worker-3
Result: bar
Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap
73
Modern Java - A Guide to Java 8
Here's another example searching solely on the values of the map:
String result = map.searchValues(1, value -> {
System.out.println(Thread.currentThread().getName());
if (value.length() > 3) {
return value;
}
return null;
});
System.out.println("Result: " + result);
//
//
//
//
//
ForkJoinPool.commonPool-worker-2
main
main
ForkJoinPool.commonPool-worker-1
Result: solo
Reduce
The method
reduce()
expressions of type
already known from Java 8 Streams accepts two lambda
BiFunction
. The first function transforms each key-value pair into a
single value of any type. The second function combines all those transformed values into a
single result, ignoring any possible
null
values.
String result = map.reduce(1,
(key, value) -> {
System.out.println("Transform: " + Thread.currentThread().getName());
return key + "=" + value;
},
(s1, s2) -> {
System.out.println("Reduce: " + Thread.currentThread().getName());
return s1 + ", " + s2;
});
System.out.println("Result: " + result);
//
//
//
//
//
//
//
//
Transform: ForkJoinPool.commonPool-worker-2
Transform: main
Transform: ForkJoinPool.commonPool-worker-3
Reduce: ForkJoinPool.commonPool-worker-3
Transform: main
Reduce: main
Reduce: main
Result: r2=d2, c3=p0, han=solo, foo=bar
I hope you've enjoyed reading the third part of my tutorial series about Java 8 Concurrency.
The code samples from this tutorial are hosted on GitHub along with many other Java 8
code snippets. You're welcome to fork the repo and try it by your own.
If you want to support my work, please share this tutorial with your friends. You should also
follow me on Twitter as I constantly tweet about Java and programming related stuff.
Part 1: Threads and Executors
Part 2: Synchronization and Locks
Part 3: Atomic Variables and ConcurrentMap
Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap
74
Modern Java - A Guide to Java 8
Java 8 API by Example: Strings, Numbers,
Math and Files
March 25, 2015
Plenty of tutorials and articles cover the most important changes in Java 8 like lambda
expressions and functional streams. But furthermore many existing classes have been
enhanced in the JDK 8 API with useful features and methods.
This article covers some of those smaller changes in the Java 8 API - each described with
easily understood code samples. Let's take a deeper look into Strings, Numbers, Math and
Files.
Slicing Strings
Two new methods are available on the String class:
join
and
chars
. The first method
joins any number of strings into a single string with the given delimiter:
String.join(":", "foobar", "foo", "bar");
// => foobar:foo:bar
The second method
chars
creates a stream for all characters of the string, so you can use
stream operations upon those characters:
"foobar:foo:bar"
.chars()
.distinct()
.mapToObj(c -> String.valueOf((char)c))
.sorted()
.collect(Collectors.joining());
// => :abfor
Not only strings but also regex patterns now benefit from streams. Instead of splitting strings
into streams for each character we can split strings for any pattern and create a stream to
work upon as shown in this example:
Pattern.compile(":")
.splitAsStream("foobar:foo:bar")
.filter(s -> s.contains("bar"))
.sorted()
.collect(Collectors.joining(":"));
// => bar:foobar
Additionally regex patterns can be converted into predicates. Those predicates can for
example be used to filter a stream of strings:
Java 8 API by Example: Strings, Numbers, Math and Files
75
Modern Java - A Guide to Java 8
Pattern pattern = Pattern.compile(".*@gmail\\.com");
Stream.of("bob@gmail.com", "alice@hotmail.com")
.filter(pattern.asPredicate())
.count();
// => 1
The above pattern accepts any string which ends with
Java 8
Predicate
@gmail.com
and is then used as a
to filter a stream of email addresses.
Crunching Numbers
Java 8 adds additional support for working with unsigned numbers. Numbers in Java had
always been signed. Let's look at
An
int
Integer
for example:
represents a maximum of 2³² binary digits. Numbers in Java are per default signed,
so the last binary digit represents the sign (0 = positive, 1 = negative). Thus the maximum
positive signed
int
is 2³¹ - 1 starting with the decimal zero.
You can access this value via
Integer.MAX_VALUE
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MAX_VALUE + 1);
:
// 2147483647
// -2147483648
Java 8 adds support for parsing unsigned ints. Let's see how this works:
long maxUnsignedInt = (1l << 32) - 1;
String string = String.valueOf(maxUnsignedInt);
int unsignedInt = Integer.parseUnsignedInt(string, 10);
String string2 = Integer.toUnsignedString(unsignedInt, 10);
As you can see it's now possible to parse the maximum possible unsigned number 2³² - 1
into an integer. And you can also convert this number back into a string representing the
unsigned number.
This wasn't possible before with
parseInt
as this example demonstrates:
try {
Integer.parseInt(string, 10);
}
catch (NumberFormatException e) {
System.err.println("could not parse signed int of " + maxUnsignedInt);
}
The number is not parseable as a signed int because it exceeds the maximum of 2³¹ - 1.
Do the Math
Java 8 API by Example: Strings, Numbers, Math and Files
76
Modern Java - A Guide to Java 8
The utility class
Math
has been enhanced by a couple of new methods for handling number
overflows. What does that mean? We've already seen that all number types have a
maximum value. So what happens when the result of an arithmetic operation doesn't fit into
its size?
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MAX_VALUE + 1);
// 2147483647
// -2147483648
As you can see a so called integer overflow happens which is normally not the desired
behavior.
Java 8 adds support for strict math to handle this problem.
Math
couple of methods who all ends with
. Those methods handle
overflows properly by throwing an
exact
, e.g.
addExact
ArithmeticException
has been extended by a
when the result of the operation
doesn't fit into the number type:
try {
Math.addExact(Integer.MAX_VALUE, 1);
}
catch (ArithmeticException e) {
System.err.println(e.getMessage());
// => integer overflow
}
The same exception might be thrown when trying to convert longs to int via
toIntExact
:
try {
Math.toIntExact(Long.MAX_VALUE);
}
catch (ArithmeticException e) {
System.err.println(e.getMessage());
// => integer overflow
}
Working with Files
The utility class
Files
was first introduced in Java 7 as part of Java NIO. The JDK 8 API
adds a couple of additional methods which enables us to use functional streams with files.
Let's deep-dive into a couple of code samples.
Listing files
The method
Files.list
operations like
filter
streams all paths for a given directory, so we can use stream
and
sorted
upon the contents of the file system.
Java 8 API by Example: Strings, Numbers, Math and Files
77
Modern Java - A Guide to Java 8
try (Stream stream = Files.list(Paths.get(""))) {
String joined = stream
.map(String::valueOf)
.filter(path -> !path.startsWith("."))
.sorted()
.collect(Collectors.joining("; "));
System.out.println("List: " + joined);
}
The above example lists all files for the current working directory, then maps each path to it's
string representation. The result is then filtered, sorted and finally joined into a string. If
you're not yet familiar with functional streams you should read my Java 8 Stream Tutorial.
You might have noticed that the creation of the stream is wrapped into a try/with statement.
Streams implement
AutoCloseable
and in this case we really have to close the stream
explicitly since it's backed by IO operations.
> The returned stream encapsulates a DirectoryStream. If timely disposal of file system
resources is required, the try-with-resources construct should be used to ensure that the
stream's close method is invoked after the stream operations are completed.
Finding files
The next example demonstrates how to find files in a directory or it's sub-directories.
Path start = Paths.get("");
int maxDepth = 5;
try (Stream stream = Files.find(start, maxDepth, (path, attr) ->
String.valueOf(path).endsWith(".js"))) {
String joined = stream
.sorted()
.map(String::valueOf)
.collect(Collectors.joining("; "));
System.out.println("Found: " + joined);
}
The method
point and
find
maxDepth
accepts three arguments: The directory path
start
is the initial starting
defines the maximum folder depth to be searched. The third argument
is a matching predicate and defines the search logic. In the above example we search for all
JavaScript files (filename ends with .js).
We can achieve the same behavior by utilizing the method
Files.walk
. Instead of passing
a search predicate this method just walks over any file.
Java 8 API by Example: Strings, Numbers, Math and Files
78
Modern Java - A Guide to Java 8
Path start = Paths.get("");
int maxDepth = 5;
try (Stream stream = Files.walk(start, maxDepth)) {
String joined = stream
.map(String::valueOf)
.filter(path -> path.endsWith(".js"))
.sorted()
.collect(Collectors.joining("; "));
System.out.println("walk(): " + joined);
}
In this example we use the stream operation
filter
to achieve the same behavior as in the
previous example.
Reading and writing files
Reading text files into memory and writing strings into a text file in Java 8 is finally a simple
task. No messing around with readers and writers. The method
Files.readAllLines
reads
all lines of a given file into a list of strings. You can simply modify this list and write the lines
into another file via
Files.write
:
List lines = Files.readAllLines(Paths.get("res/nashorn1.js"));
lines.add("print('foobar');");
Files.write(Paths.get("res/nashorn1-modified.js"), lines);
Please keep in mind that those methods are not very memory-efficient because the whole
file will be read into memory. The larger the file the more heap-size will be used.
As an memory-efficient alternative you could use the method
Files.lines
. Instead of
reading all lines into memory at once, this method reads and streams each line one by one
via functional streams.
try (Stream stream = Files.lines(Paths.get("res/nashorn1.js"))) {
stream
.filter(line -> line.contains("print"))
.map(String::trim)
.forEach(System.out::println);
}
If you need more fine-grained control you can instead construct a new buffered reader:
Path path = Paths.get("res/nashorn1.js");
try (BufferedReader reader = Files.newBufferedReader(path)) {
System.out.println(reader.readLine());
}
Or in case you want to write to a file simply construct a buffered writer instead:
Java 8 API by Example: Strings, Numbers, Math and Files
79
Modern Java - A Guide to Java 8
Path path = Paths.get("res/output.js");
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
writer.write("print('Hello World');");
}
Buffered readers also have access to functional streams. The method
lines
construct a
functional stream upon all lines denoted by the buffered reader:
Path path = Paths.get("res/nashorn1.js");
try (BufferedReader reader = Files.newBufferedReader(path)) {
long countPrints = reader
.lines()
.filter(line -> line.contains("print"))
.count();
System.out.println(countPrints);
}
So as you can see Java 8 provides three simple ways to read the lines of a text file, making
text file handling quite convenient.
Unfortunately you have to close functional file streams explicitly with try/with statements
which makes the code samples still kinda cluttered. I would have expected that functional
streams auto-close when calling a terminal operation like
count
or
collect
since you
cannot call terminal operations twice on the same stream anyway.
I hope you've enjoyed this article. All code samples are hosted on GitHub along with plenty
of other code snippets from all the Java 8 articles of my blog. If this post was kinda useful to
you feel free to star the repo and follow me on Twitter.
Keep on coding!
Java 8 API by Example: Strings, Numbers, Math and Files
80
Modern Java - A Guide to Java 8
Avoiding Null Checks in Java 8
March 15, 2015
How to prevent the famous
NullPointerException
in Java? This is one of the key questions
every Java beginner will ask sooner or later. But also intermediate and expert programmers
get around this error every now and then. It's by far the most prevalent kind of error in Java
and many other programming languages as well.
Tony Hoare, the inventor of the null reference apologized in 2009 and denotes this kind of
errors as his billion-dollar mistake.
> I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that
time, I was designing the first comprehensive type system for references in an object
oriented language (ALGOL W). My goal was to ensure that all use of references should be
absolutely safe, with checking performed automatically by the compiler. But I couldn't resist
the temptation to put in a null reference, simply because it was so easy to implement. This
has led to innumerable errors, vulnerabilities, and system crashes, which have probably
caused a billion dollars of pain and damage in the last forty years.
Anyways, we have to deal with it. So what can we do to prevent NullPointerExceptions at
all? Well, the obvious answer is to add null checks all around the place. Since null checks
are kinda cumbersome and painful many languages add special syntax for handling null
checks via null coalescing operators - also known as elvis operator in languages like Groovy
or Kotlin.
Unfortunately Java doesn't provide such a syntactic sugar. But luckily things get better in
Java Version 8. This post describes a couple of techniques how to prevent writing needless
null checks by utilizing new features of Java 8 like lambda expressions.
Improving Null Safety in Java 8
I've already shown in another post how we can utilize the
Optional
type of Java 8 to
prevent null checks. Here's the example code from the original post.
Assuming we have a hierarchical class structure like this:
Avoiding Null Checks in Java 8
81
Modern Java - A Guide to Java 8
class Outer {
Nested nested;
Nested getNested() {
return nested;
}
}
class Nested {
Inner inner;
Inner getInner() {
return inner;
}
}
class Inner {
String foo;
String getFoo() {
return foo;
}
}
Resolving a deep nested path in this structure can be kinda awkward. We have to write a
bunch of null checks to make sure not to raise a
NullPointerException
:
Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
System.out.println(outer.nested.inner.foo);
}
We can get rid of all those null checks by utilizing the Java 8
map
accepts a lambda expression of type
result into an
Optional
Function
Optional
type. The method
and automatically wraps each function
. That enables us to pipe multiple
map
operations in a row. Null
checks are automatically handled under the hood.
Optional.of(new Outer())
.map(Outer::getNested)
.map(Nested::getInner)
.map(Inner::getFoo)
.ifPresent(System.out::println);
An alternative way to achieve the same behavior is by utilizing a supplier function to resolve
the nested path:
Outer obj = new Outer();
resolve(() -> obj.getNested().getInner().getFoo());
.ifPresent(System.out::println);
Calling
obj.getNested().getInner().getFoo())
might throw a
case the exception will be caught and the method returns
Avoiding Null Checks in Java 8
NullPointerException
Optional.empty()
. In this
.
82
Modern Java - A Guide to Java 8
public static Optional resolve(Supplier resolver) {
try {
T result = resolver.get();
return Optional.ofNullable(result);
}
catch (NullPointerException e) {
return Optional.empty();
}
}
Please keep in mind that both solutions are probably not as performant as traditional null
checks. In most cases that shouldn't be much of an issue.
As usual the above code samples are hosted on GitHub.
Happy coding!
> UPDATE: I've updated the code samples thanks to a hint from Zukhramm on Reddit.
Avoiding Null Checks in Java 8
83
Modern Java - A Guide to Java 8
Fixing Java 8 Stream Gotchas with IntelliJ
IDEA
March 05, 2015
Java 8 has been released almost one year ago in March 2014. At Pondus we've managed to
update all of our production servers to this new version back in May 2014. Since then we've
migrated major parts of our code base to lambda expressions, streams and the new Date
API. We also use Nashorn to dynamically script parts of our application which may change
during runtime.
The most used feature besides lambdas is the new Stream API. Collection operations are all
around the place in almost any codebase I've ever seen. And Streams are a great way to
improve code readability of all those collection crunching.
But one thing about streams really bothers me: Streams only provide a few terminal
operations like
and
findFirst
directly while others are only accessible via
. There's a utility class Collectors, providing a bunch of convenient collectors like
collect
toList
reduce
,
toSet
,
joining
and
groupingBy
.
For example this code filters over a collection of strings and creates a new list:
stringCollection
.stream()
.filter(e -> e.startsWith("a"))
.collect(Collectors.toList());
After migrating a project with 300k lines of code to streams I can say that
and
groupingBy
toList
,
toSet
are by far the most used terminal operations in our project. So I really
cannot understand the design decision not to integrate those methods directly into the
Stream
interface so you could just write:
stringCollection
.stream()
.filter(e -> e.startsWith("a"))
.toList();
This might look like a minor imperfection at first but it gets really annoying if you have to use
this kind of stuff over and over again.
There's a method
toArray()
but no
toList()
. So I really hope some of the more
convenient collectors will make it's way into the
Fixing Java 8 Stream Gotchas with IntelliJ IDEA
Stream
interface in Java 9. Brian?
_
84
Modern Java - A Guide to Java 8
> As a side note: Stream.js is a JavaScript port of the Java 8 Streams API for the browser
and addresses the described issue nicely. All important terminal operations are directly
accessible on the stream itself for convenience. See the API doc for details.
Anyways. IntelliJ IDEA claims to be the most intelligent Java IDE. So let's see how we can
utilize IDEA to solve this problem for us.
IntelliJ IDEA to the rescue
IntelliJ IDEA comes with a handy feature called Live Templates. If you don't already know
what it is: Live Templates are shortcuts for commonly used code snippets. E.g. you type
sout
+ tabulator and IDEA inserts the code snippet
System.out.println()
. Read here to
learn more about it.
How does Live Templates help with the problem described above? Actually we can simply
create our own Live Templates for all the commonly used default Stream collectors. E.g. we
can create a Live Template with the abbreviation
.collect(Collectors.toList())
.toList
to insert the appropriate collector
automatically.
This is how it looks like in action:
Set up your own Live Templates
Let's see how we can set this up. First go to Settings and choose Live Templates in the
menu to the left. You can also use the handy filter input at the top left of the dialog.
Fixing Java 8 Stream Gotchas with IntelliJ IDEA
85
Modern Java - A Guide to Java 8
Next we can create a new group called
Stream
via the
+
icon on the right. Next we add all
of our stream-related Live Templates to this group. I'm using the default collectors
toSet
,
groupingBy
and
join
toList
,
quite commonly, so I create a new Live Template for each of
those methods.
This part is important: After adding a new Live Template you have to specify the applicable
context at the bottom of the dialog. You have to choose Java → Other. Afterwards you
define the abbreviation, a description and the actual template code.
// Abbreviation: .toList
.collect(Collectors.toList())
// Abbreviation: .toSet
.collect(Collectors.toSet())
// Abbreviation: .join
.collect(Collectors.joining("$END$"))
// Abbreviation: .groupBy
.collect(Collectors.groupingBy(e -> $END$))
The special variable
$END$
determines the cursors position after using the template, so you
can directly start typing at this position, e.g. to define the joining delimiter.
Fixing Java 8 Stream Gotchas with IntelliJ IDEA
86
Modern Java - A Guide to Java 8
> Hint: You should enable the option "Add unambiguous imports on the fly" so IDEA
automatically adds an import statement to
java.util.stream.Collectors
. The option is
located in: Editor → General → Auto Import
Let's see those two templates in action:
Join
GroupBy
Live Templates in IntelliJ IDEA are an extremely versatile and powerful tool. You can greatly
increase your coding productivity with it. Do you know other examples where Live Templates
can save your live? Let me know!
Still not satisfied? Learn everything you ever wanted to know about Java 8 Streams in my
Streams Tutorial.
Happy coding.
Fixing Java 8 Stream Gotchas with IntelliJ IDEA
87
Modern Java - A Guide to Java 8
Using Backbone.js with Nashorn
April 07, 2014
This example demonstrates how to use Backbone.js models with the Java 8 Nashorn
Javascript Engine. First released in March 2014 as part of Java SE 8, Nashorn extends
Javas capabilities by running javascript code natively on the JVM. For java web developers
Nashorn might be especially useful for re-using existing client-side code on the java server.
Traditionally Node.js was in a clear advantage, but Nashorns possibilities might close the
gap to the JVM.
When working with modern javascript MVC frameworks like Backbone.js for HTML5 frontends, more and more code moves from the server back-end to the web front-end. This
approach can greatly increase the user experience because you safe a lot of serverroundtrips when using business logic from your views.
Backbone enables you to define model classes which can be bound to views (e.g. HTML
forms). Backbone keeps track of updating the model when the user interacts with the UI and
vice versa. It also aids you by synchronizing your model with the server, e.g. by calling the
appropriate method of your REST handler on the server side. So you end up implementing
business logic in your front-end code, leaving your server model responsible for persisting
data.
Reusing backbone models on the server side is quite easy with Nashorn, as the following
example will demonstrate. Before we start make sure you're familiar with writing javascript
for the Nashorn Engine by reading my Nashorn Tutorial.
The Java Model
First, we define a domain class
Product
in java code. This class might be used for CRUD
database operations (saving to and loading from a datasource). Keep in mind that this class
is a dumb Java Bean without any business logic applied, because we want our front-end to
be capable of executing the business logic right from the UI.
class Product {
String name;
double price;
int stock;
double valueOfGoods;
}
The Backbone Model
Using Backbone.js with Nashorn
88
Modern Java - A Guide to Java 8
Now we define the backbone model as the counter-part of our java bean. The backbone
model
Product
uses the same data-structure as the java bean, since this is the data we
might want to persist on the java server.
The backbone model also implements the business logic: The method
calculates the value of all products by multiplying
price
changes the property
valueOfGoods
stock
with
price
getValueOfGoods
. Each time
stock
or
must be re-calculated.
var Product = Backbone.Model.extend({
defaults: {
name: '',
stock: 0,
price: 0.0,
valueOfGoods: 0.0
},
initialize: function() {
this.on('change:stock change:price', function() {
var stock = this.get('stock');
var price = this.get('price');
var valueOfGoods = this.getValueOfGoods(stock, price);
this.set('valueOfGoods', valueOfGoods);
});
},
getValueOfGoods: function(stock, price) {
return stock * price;
}
});
Since the backbone model doesn't use any Nashorn language extensions, we can safely
use the same code both on the client (Browser) and on the server (Java) side.
Keep in mind that I deliberately chose a really simple function for demonstrating purposes
only. Real business logic should be more complex.
Putting both together
The next goal is to re-use the backbone model from Nashorn, e.g. on the java server. We
want to achieve the following behavior: bind all properties from the java bean to the
backbone model; calculate the property
valueOfGoods
; pass the result back to java.
First, we create a new script to be evaluated solely by Nashorn, so we can safely use
Nashorn extensions here:
Using Backbone.js with Nashorn
89
Modern Java - A Guide to Java 8
load('http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js');
load('http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.2/backbone-min.js');
load('product-backbone-model.js');
var calculate = function(javaProduct) {
var model = new Product();
model.set('name', javaProduct.name);
model.set('price', javaProduct.price);
model.set('stock', javaProduct.stock);
return model.attributes;
};
The script first loads the relevant external scripts Underscore and Backbone (Underscore is
a pre-requirement for Backbone) and our
The function
calculate
created backbone
accepts a
Product
backbone model as defined above.
java bean, binds all properties to a newly
and returns all attributes of the model back to the caller. By
Product
setting the properties
Product
stock
and
price
on the backbone model, property
valueOfGoods
will automatically be calculated due to the event handler registered in the models
initialize
constructor function.
Finally, we call the function
calculate
from java:
Product product = new Product();
product.setName("Rubber");
product.setPrice(1.99);
product.setStock(1337);
ScriptObjectMirror result = (ScriptObjectMirror)
invocable.invokeFunction("calculate", product);
System.out.println(result.get("name") + ": " + result.get("valueOfGoods"));
// Rubber: 2660.63
We create a new
method
Product
getValueOfGoods
java bean and pass it to the javascript function. As a result the
will be triggered, so we can read the property
valueOfGoods
from
the returning object.
Conclusion
Reusing existing javascript libraries on the Nashorn Engine is quite easy. Backbone is great
for building complex HTML5 front-ends. In my opinion Nashorn and the JVM now is a great
alternative to Node.js, since you can make use of the whole Java eco-system in your
Nashorn codebase, such as the whole JDK API and all available libraries and tools. Keep in
mind that you're not tight to the Java Language when working with Nashorn - think Scala,
Groovy, Clojure or even pure Javascript via
jjs
.
The runnable source code from this article is hosted on GitHub (see this file). Feel free to
fork the repository or send me your feedback via Twitter.
Using Backbone.js with Nashorn
90
Source Exif Data:
File Type : PDF File Type Extension : pdf MIME Type : application/pdf PDF Version : 1.4 Linearized : No Author : wizardforcel Create Date : 2016:04:16 02:33:35+00:00 Producer : calibre 2.41.0 [http://calibre-ebook.com] Description : Author: winterbe Title : Modern Java - A Guide to Java 8 Creator : wizardforcel Publisher : GitBook Subject : Language : en Metadata Date : 2016:04:16 02:33:35.519649+00:00 Timestamp : 2016:04:16 02:33:31.995160+00:00 Page Count : 90EXIF Metadata provided by EXIF.tools