A Quick Tour of Java 8 Features

This section provides an overview of Java 8’s primary new features— with code examples—to give you an idea of what’s available.

Lambda Expressions

Lambda expressions let you pass around a piece of code in a concise way. For example, say you need to get a Thread to perform a task. You could do so by creating a Runnable object, which you then pass as an argument to the Thread:

Runnable runnable = new Runnable() {    
  public void run() {        
new Thread(runnable).start(); 

Using lambda expressions, on the other hand, you can rewrite the previous code in a much more readable way:

new Thread(() -> System.out.println("Hi")).start(); 

You’ll learn about lambda expressions in much greater detail in later sections.

Method References

Method references make up a new feature that goes hand in hand with lambda expressions. They let you select an existing method defined in a class and pass it around. For example, say you need to compare a list of strings by ignoring case. Currently, you would write code that looks like this:

List strs = Arrays.asList("C", "a", "A", "b");
Collections.sort(strs, new Comparator() { 
  public int compare(String s1, String s2) { 
     return s1.compareToIgnoreCase(s2);

The code just shown is extremely verbose. After all, all you need is the method compareToIgnoreCase. Using method references, you can explicitly say that the comparison should be performed using the method compareToIgnoreCase defined in the String class:

Collections.sort(strs, String::compareToIgnoreCase); 

The code String::compareToIgnoreCase is a method reference. It uses the special syntax ::


Nearly every Java application creates and processes collections. They’re fundamental to many programming tasks since they let you group and process data. However, working with collections can be quite verbose and difficult to parallelize. The following code illustrates how verbose processing collections can be. It processes a list of invoices to find the IDs of training-related invoices sorted by the invoice’s amount:

List trainingInvoices = new ArrayList<>();
for (Invoice inv : invoices) {
	if (inv.getTitle().contains("Training")) {
Collections.sort(trainingInvoices, new Comparator() {
	public int compare(Invoice inv1, Invoice inv2) {
		return inv2.getAmount().compareTo(inv1.getAmount());
List invoiceIds = new ArrayList<>();
for (Invoice inv : trainingInvoices) {

Java 8 introduces a new abstraction called Stream that lets you process data in a declarative way. In Java 8, you can refactor the preceding code using streams, like so:

List invoiceIds = invoices.stream()
	.filter(inv -> inv.getTitle().contains("Training"))

In addition, you can explicitly execute a stream in parallel by using the method parallelStream instead of stream from a collection source.

Enhanced Interfaces

Interfaces in Java 8 can now declare methods with implementation code thanks to two improvements. First, Java 8 introduces default methods, which let you declare methods with implementation code inside an interface. They were introduced as a mechanism to evolve the Java API in a backward-compatible way. For example, you’ll see that in Java 8 the List interface now supports a sort method that is defined as follows:

default void sort(Comparator c) { 
  Collections.sort(this, c);

Default methods can also serve as a multiple inheritance mechanism for behavior. In fact, prior to Java 8, a class could already implement multiple interfaces. Now, you can inherit default methods from multiple different interfaces. Note that Java 8 has explicit rules to prevent inheritance issues common in C++ (such as the diamond problem). Second, interfaces can now also have static methods. It’s a common pattern to define both an interface and a companion class defining static methods for working with instances of the interface. For example, Java has the Collection interface and the Collections class, which defines utility static methods. Such utility static methods can now live within the interface. For instance, the Stream interface in Java 8 declares a static method like this:

public static Stream of(T... values) { 
  return Arrays.stream(values); 


Java 8 introduces a new class called Optional. Inspired by functional programming languages, it was introduced to allow better modeling in your codebase when a value may be present or absent. Think of it as a single-value container, in that it either contains a value or is empty. Optional has been available in alternative collections frameworks (like Guava), but is now available as part of the Java API. The other benefit of Optional is that it can protect you against NullPointerExceptions. In fact, Optional defines methods to force you to explicitly check the absence or presence of a value. Take the following code as an example:


If getEventWithId(10) returns null, then the code throws a NullPointerException. If getLocation() returns null, then it also throws a NullPointerException. In other words, if any of the methods return null, a NullPointerException could be thrown. You can avoid this by adopting defensive checks, like the following:

public String getCityForEvent(int id) {    
  Event event = getEventWithId(id);
  if(event != null) {        
    Location location = event.getLocation();        
    if(location != null) {            
     return location.getCity();        
  return "TBC"; 

In this code, an event may have an associated location. However, a location always has an associated city. Unfortunately, it’s often easy to forget to check for a null value. In addition, the code is now more verbose and harder to follow. Using Optional, you can refactor the code to be more concise and explicit, like so:

public String getCityForEvent(int id) {    

At any point, if a method returns an empty Optional, you get the default value "TBC".


Java-8 12 Stream 12


Explore Tutu'rself