Table of Contents
- 1. Overview
- 2. Lambda Expressions
- 3. Default Methods
- 4. Streams
- 5. Optional
- 6. Nashorn
Like many Java developers, the first time I heard about lambda expressions it piqued my interest. Also like many others, I was disappointed when it was set back. However, it is better late than never.
Java 8 is a giant step forward for the Java language. Writing this book has forced me to learn a lot more about it. In Project Lambda, Java gets a new closure syntax, method-references, and default methods on interfaces. It manages to add many of the features of functional languages without losing the clarity and simplicity Java developers have come to expect.
I would like to acknowledge the following people for providing valuable resources:
- Brian Goetz – “State of the Lambda”
- Aleksey Shipilev – jdk8-lambda-samples
- Richard Warburton – “Java 8 Lambdas”
- Julien Ponge – “Oracle Nashorn” in the Jan./Feb. 2014 issue of Java Magazine.
- Venkat Subramaniam – agiledeveloper.com
- All of the developers behind Java 8.
- The developers of Guava, joda-time, Groovy, and Scala.
This book is a short introduction to Java 8. After reading it, you should have a basic understanding of the new features and be ready to start using it.
This book assumes that you have a good understanding of Java the language and the JVM. If you’re not familiar with the language, including features of Java 7, it might be hard to follow some of the examples. For a more comprehensive introduction to programming in Java you should check out Modern Programming Made Easy or Modern Java: Second Edition.
Java 8 includes the following:
- Lambda expressions
- Method references
- Default Methods (Defender methods)
- A new Stream API.
- A new Date/Time API.
- Removal of the Permanent Generation
- and more…
The best way to read this book is with a Java 8 supporting IDE running so you can try out the new features.
2. Lambda Expressions
The biggest new feature of Java 8 is language level support for lambda expressions (Project Lambda). A lambda expression is like syntactic sugar for an anonymous class1 with one method whose type is inferred. However, it will have enormous implications for simplifying development.
The main syntax of a lambda expression is “parameters -> body”. The compiler can usually use the context of the lambda expression to determine the functional interface2 being used and the types of the parameters. There are four important rules to the syntax:
- Declaring the types of the parameters is optional.
- Using parentheses around the parameter is optional if you have only one parameter.
- Using curly braces is optional (unless you need multiple statements).
- The “return” keyword is optional if you have a single expression that returns a value.
Here are some examples of the syntax:
The last expression could be used to sort a list; for example:
In this case the lambda expression implements the
Comparator interface to sort strings by length.
Here’s a short example of using lambdas with the Runnable interface:
The important thing to note is both the r1 and r2 lambdas call the
toString() method of the Hello class. This demonstrates the scope available to the lambda.
You can also refer to final variables or effectively final variables. A variable is effectively final if it is only assigned once.
For example, using Spring’s HibernateTemplate:
In the above, you can refer to the variable
sql because it is only assigned once.
If you were to assign to it a second time, it would cause a compilation error.
2.3 Method references
Since a lambda expression is like an object-less method, wouldn’t be nice if we could refer to existing methods instead of using a lamda expression? This is exactly what we can do with method references.
For example, imagine you frequently need to filter a list of Files based on file types. Assume you have the following set of methods for determining a file’s type:
Whenever you want to filter a list of files, you can use a method reference as in the following example
(assuming you already defined a method
getFiles() that returns a
Method references can point to:
- Static methods.
- Instance methods.
- Methods on particular instances.
- Constructors (ie.
For example, using the new
The above reads the file “Nio.java”, calls
trim() on every line, and then prints out the lines.
System.out::println refers to the
println method on an instance of
2.4 Functional Interfaces
In Java 8 a functional interface is defined as an interface with exactly one abstract method. This even applies to interfaces that were created with previous versions of Java.
Java 8 comes with several new functional interfaces in the package,
<T,R>- takes an object of type T and returns R.
<T>- just returns an object of type T.
<T>- returns a boolean value based on input of type T.
<T>- performs an action with given object of type T.
- BiFunction - like Function but with two parameters.
- BiConsumer - like Consumer but with two parameters.
It also comes with several corresponding interfaces for primitive types, such as:
The coolest thing about functional interfaces is that they can be assigned to anything that would fulfill their contract. Take the following code for example:
This code is perfectly valid Java 8. The first line defines a function that prepends “@” to a String. The last two lines define functions that do the same thing: get the length of a String.
The Java compiler is smart enough to convert the method reference to String’s
length() method into a
Function (a functional interface) whose
apply method takes a String and returns an Integer.
This would print out the lengths of the given strings.
Any interface can be functional interface, not merely those that come with Java.
To declare your intention that an interface is functional, use the
Although not necessary, it will cause a compilation error if your interface does not satisfy the requirements (ie. one abstract method).
2.5 Comparisons to Java 7
To better illustrate the benefit of Lambda-expressions, here are some examples of how code from Java 7 can be shortened in Java 8.
Creating an ActionListener
Printing out a list of Strings
Sorting a list of Strings
For the sorting examples, assume you have the following
Here’s how you might sort this list in Java 7 by last-name and then first-name:
In Java 8, this can be shortened to the following:
3. Default Methods
In order to add the
stream method (or any others) to the core Collections API, Java needed another new feature, Default methods (also known as Defender Methods or Virtual Extension methods).
This way they could add new methods to the
List interface for example without breaking all the existing implementations (backwards compatibility).
Default methods can be added to any interface. Like the name implies, any class that implements the interface but does not override the method will get the default implementation.
For example, the
stream method in the
Collection interface is defined something like the following:
You can always override a default method if you need different behavior.
3.1 Default and Functional
An interface can have one or more default methods and still be functional.
For example, take a look at the Iterable interface:
It has both the
iterator() method and the
3.2 Multiple Defaults
In the unlikely case that your class implements two or more interfaces that define the same default method, Java will throw a compilation error. You will need to override the method and choose from one of the methods. For example:
In the above code,
talk is overridden and calls
Foo’s talk method.
This is similar to the way you refer to a super class in pre-Java-8.
3.3 Static Methods on Interface
Although not strictly related to default methods, the ability to add static methods to interfaces is a similar change to the Java language.
For example, there are many static methods on the new Stream interface. This makes “helper” methods easier to find since they can be located directly on the interface, instead of a different class such as StreamUtil or Streams.
Here’s an example in the new Stream interface:
The above method creates a new stream based on the given values.
Stream interface is such a fundamental part of Java 8 it deserves its own chapter.
4.1 What is a Stream?
Stream interface is located in the
It represents a sequence of objects somewhat like the Iterator interface.
However, unlike the Iterator, it supports parallel execution.
The Stream interface supports the map/filter/reduce pattern and executes lazily, forming the basis (along with lambdas) for functional-style programming in Java 8.
There are also corresponding primitive streams (IntStream, DoubleStream, and LongStream) for performance reasons.
4.2 Generating Streams
There are many ways to create a Stream in Java 8. Many of the existing Java core library classes have Stream returning methods in Java 8.
The most obvious way to create a stream is from a
The Collection interface has two default methods on it for creating streams:
- stream(): Returns a sequential Stream with the collection as its source.
- parallelStream(): Returns a possibly parallel Stream with the collection as its source.
The ordering of the Stream relies on the underlying collection just like an Iterator.
BufferedReader now has the
lines() method which returns a Stream; for example3:
You can also read a file as a Stream using
Files.lines(Path filePath); for example:
Note this populates lazily; it does not read the entire file when you call it.
Streaming File Trees
There are several static methods on the
Files class for navigating file trees using a Stream.
list(Path dir)– Stream of files in the given directory.
walk(Path dir)4 – Stream that traverses the file tree depth-first starting at the given directory.
walk(Path dir, int maxDepth)– Same as walk(dir) but with a maximum depth.
Streaming Text Patterns
The Pattern class now has a method,
splitAsStream(CharSequence), which creates a Stream.
The above uses a very simple pattern, a comma, and splits the text into a stream and prints it out. This would produce the following output:
iterate static methods on Stream, you can create a Stream of values including never ending streams.
For example, you could call generate in the following way to create an infinite supply of objects:
For example, you could use this technique to produce a stream of CPU load or memory usage. However, you should use this with caution. It is similar to an infinite loop.
You could also use
generate to create an infinite random number supply; for example:
java.util.Random class does this for you with the following new methods:
Each of those methods is overloaded with definitions similar to the following:
ints(): An infinite Stream of random integers.
ints(int n, int m): An infinite Stream of random integers from n (inclusive) to m (exclusive).
ints(long size): A Stream of given size of random integers.
ints(long size, int n, int m): A Stream of given size of random integers with given bounds.
iterate method is similar to
generate except it takes an initial value and a Function that modifies that value.
For example, you can iterate over the Integers using the following code:
This would print out “1234…” continuously until you stop the program.
There are also new methods for creating ranges of numbers as Streams.
For example, the static method,
range, on the
The above would print out the numbers one through ten.
Each primitive Stream (IntStream, DoubleStream, and LongStream) has a corresponding
You can create a Stream from any number of elements or an array using the two following methods:
Stream.of can take any number of parameters of any type.
4.3 For Each
The most basic thing you can do with a Stream is loop through it using the
For example, to print out all of the files in the current directory, you could do the following:
For the most part, this replaces the “for loop”. It is more concise, and more object-oriented since you are delegating the implementation of the actual loop.
Lambda expressions and default methods allow us to implement map/filter/reduce in Java 8. Actually it is already implemented for us in the standard library.
For example, imagine you want to get the current point scores from a list of player-names and find the player with the most points.
You have a simple class,
PlayerPoints, and a
getPoints method defined as the following:
Finding the highest player could be done very simply in Java 8 as shown in the following code:
This could also be done in Java 7 with the
dollar library (or similarly with Guava or Functional-Java), but it would be much more verbose as shown in the following:
The major benefit to coding this way (apart from the reduction in lines of code) is the ability to hide the underlying implementation of map/reduce. For example, it’s possible that map and reduce are implemented concurrently, allowing you to easily take advantage of multiple processors. We’ll describe one way to do this (ParallelArray) in the following section.
4.5 Parallel Array
ParallelArray was part of JSR-166, but ended up being excluded from the standard Java lib.
It does exist and was released to the public domain (you can download it from the JSR website).
Although it was already out there, it really wasn’t easy to use until closures were included in the Java language. In Java 7 using the ParallelArray looks like the following:
In Java 8, you can do the following:
However, Java 8’s addition of stream() and parallelStream() make this even easier:
This makes it extremely simple to switch between a sequential implementation and a concurrent one.
You can peek into a stream to do some action without interrupting the stream.
For example you could print out elements to debug code:
You can use any action you want, but you should not try to modify elements; you should use
limit(int n) method can be used to limit a stream to the given number of elements.
The above would print out ten random integers.
Stream also has the
sorted() method for sorting a stream.
Like all intermediate methods on Stream (such as
sorted() method executes lazily.
Nothing happens until a terminating operation (such as
forEach) is called.
However, you should call a limiting operation like
limit before calling
sorted() on an infinite stream.
For example, the following would throw a runtime exception (using build 1.8.0-b132):
However, the following code works just fine:
Also, you should call
sorted() after any calls to
For example, this code prints out the first five Java file-names in the current directory:
The code above does the following:
- Lists the files in the current directory.
- Maps those files to file names.
- Finds names that end with “.java”.
- Takes only the first five (sorted alphabetically).
- Prints them out.
4.9 Collectors and Statistics
Since Streams are lazily evaluated and support parallel execution, you need a special way to combine results; this is called a Collector.
A Collector represents a way to combine the elements of a Stream into one result. It consists of three things:
- A supplier of an initial value.
- An accumulator which adds to the initial value.
- A combiner which combines two results into one.
There are two ways to do this:
collect(Collector) (types left off for brevity).
Luckily, Java 8 comes with several Collectors built in. Import them the following way:
The simplest collectors are things like toList() and toCollection():
If you’re familiar with Apache Commons’
joining collector is similar to it.
It combines the stream using a given delimiter.
This would combine all of the names into one String separated by commas.
More complex collectors resolve to a single value. For example, you can use an “averaging” Collector to get the average; for example:
The above code calculates the average length of non-empty lines in the file “Nio.java”.
Sometimes you want to collect multiple statistics about a collection.
Because Streams are consumed when you call
collect, you need to calculate all of your statistics at once.
This is where SummaryStatistics comes in.
First import the one you want to use:
Then use the
summarizingInt collector; for example:
The above code performs the same average as before, but also computes the maximum, minimum, and count of the elements.
Equivalently, you can map your stream to a primitive type and then call
4.10 Grouping and Partitioning
groupingBy collector groups elements based on a function you provide.
partitioningBy method creates a map with a boolean key.
4.11 Comparisons to Java 7
To better illustrate the benefit of Streams in Java 8, here are some examples of code from Java 7 compared to their new versions.
Finding a maximum
Calculating an average
Printing the numbers one through ten
Java 8 comes with the
Optional class in the
java.util package for avoiding null return values (and thus
It is very similar to Google Guava’s Optional,
which is similar to Nat Pryce’s Maybe class
You can use
Optional.of(x) to wrap a non-null value,
Optional.empty() to represent a missing value,
Optional.ofNullable(x) to create an Optional from a reference that may or may not be null.
After creating an instance of Optional, you then use
isPresent() to determine if the there is a value and
get() to get the value.
Optional provides a few other helpful methods for dealing with missing values:
orElse(T)– Returns the given default value if the Optional is empty.
orElseGet(Supplier<T>)– Calls on the given Supplier to provide a value if the Optional is empty.
orElseThrow(Supplier<X extends Throwable>)– Calls on the given Supplier for an exception to throw if the Optional is empty.
It also includes functional style (lambda friendly) methods, like the following:
filter(Predicate<? super T> predicate)– Filters the value and returns a new Optional.
flatMap(Function<? super T,Optional<U>> mapper)– Performs a mapping operation which returns an Optional.
ifPresent(Consumer<? super T> consumer)– Executes the given Consumer only if there is a value present (no return value).
map(Function<? super T,? extends U> mapper)– Uses the given mapping Function and returns a new Optional.
Nashorn is much faster since it uses the
invokedynamic feature of the JVM.
It also includes a command line tool (
JDK 8 includes the command line tool
This can be useful for running scripts; for example, let’s say you wanted to quickly find the sum of some numbers:
Running the above code should print out
Running jjs with the
You can also embed variables into strings and have them evaluate; for example:
This would print out the current date and time.
First, you need to import the ScriptEngine:
Second, you use the
ScriptEngineManager to get the Nashorn engine:
eval method can also take a
FileReader as input:
You can import and use Java classes and packages using the JavaImporter.
For example, import
java.util, the IO, and NIO file packages:
The above demonstrates that
paths is an instance of
LinkedList and prints out the list.
Later on you could add the following code to write text into the files:
We can use existing Java classes, but we can also create new ones.
You can extend Java classes and interfaces using
For example, you can extend the Callable interface and implement the
Firstly, you need to cast the engine to the Invocable interface:
Then, to invoke any function, simple use the
invokeFunction method, for example:
Lastly, you can use the
For example, if you have the following
JPrinter interface, you can use it like so:
1A lambda expression is not an anonymous class; it actually uses
invokedynamic in the byte-code.↩
2We will explain what “functional interface” means in a later section.↩
3Of course you should add a catch statement to this for error handling.↩
4The actual method signature is
walk(Path start, FileVisitOption... options) but you will probably just use