Chapter 1. The Basics

The biggest change in Java 8 is the addition of concepts from functional programming to the language. Specifically, the language added lambda expressions, method references, and streams.

If you haven’t used the new functional features yet, you’ll probably be surprised by how different your code will look from previous Java versions. The changes in Java 8 represent the biggest changes to the language ever. In many ways, it feels like you’re learning a completely new language.

The question then becomes: Why do this? Why make such drastic changes to a language that’s already twenty years old and plans to maintain backward compatibility? Why make such dramatic revisions to a language that has been, by all accounts, extremely successful? Why switch to a functional paradigm after all these years of being one of the most successful object-oriented languages ever?

The answer is that the software development world has changed, so languages that want to be successful in the future need to adapt as well. Back in the mid-’90s, when Java was shiny and new, Moore’s law1 was still fully in force. All you had to do was wait a couple of years and your computer would double in speed.

Today’s hardware no longer relies on increasing chip density for speed. Instead, even most phones have multiple cores, which means software needs to be written expecting to be run in a multiprocessor environment. Functional programming, with its emphasis on “pure” functions (that return the same result given the same inputs, with no side effects) and immutability simplifies programming in parallel environments. If you don’t have any shared, mutable state, and your program can be decomposed into collections of simple functions, it is easier to understand and predict its behavior.

This, however, is not a book about Haskell, or Erlang, or Frege, or any of the other functional programming languages. This book is about Java, and the changes made to the language to add functional concepts to what is still fundamentally an object-oriented language.

Java now supports lambda expressions, which are essentially methods treated as though they were first-class objects. The language also has method references, which allow you to use an existing method wherever a lambda expression is expected. In order to take advantage of lambda expressions and method references, the language also added a stream model, which produces elements and passes them through a pipeline of transformations and filters without modifying the original source.

The recipes in this chapter describe the basic syntax for lambda expressions, method references, and functional interfaces, as well the new support for static and default methods in interfaces. Streams are discussed in detail in Chapter 3.

1.1 Lambda Expressions

Problem

You want to use lambda expressions in your code.

Solution

Use one of the varieties of lambda expression syntax and assign the result to a reference of functional interface type.

Discussion

A functional interface is an interface with a single abstract method (SAM). A class implements any interface by providing implementations for all the methods in it. This can be done with a top-level class, an inner class, or even an anonymous inner class.

For example, consider the Runnable interface, which has been in Java since version 1.0. It contains a single abstract method called run, which takes no arguments and returns void. The Thread class constructor takes a Runnable as an argument, so an anonymous inner class implementation is shown in Example 1-1.

Example 1-1. Anonymous inner class implementation of Runnable
public class RunnableDemo {
    public static void main(String[] args) {
        new Thread(new Runnable() {  1
            @Override
            public void run() {
                System.out.println(
                    "inside runnable using an anonymous inner class");
            }
        }).start();
    }
}
1

Anonymous inner class

The anonymous inner class syntax consists of the word new followed by the Runnable interface name and parentheses, implying that you’re defining a class without an explicit name that implements that interface. The code in the braces ({}) then overrides the run method, which simply prints a string to the console.

The code in Example 1-2 shows the same example using a lambda expression.

Example 1-2. Using a lambda expression in a Thread constructor
new Thread(() -> System.out.println(
    "inside Thread constructor using lambda")).start();

The syntax uses an arrow to separate the arguments (since there are zero arguments here, only a pair of empty parentheses is used) from the body. In this case, the body consists of a single line, so no braces are required. This is known as an expression lambda. Whatever value the expression evaluates to is returned automatically. In this case, since println returns void, the return from the expression is also void, which matches the return type of the run method.

A lambda expression must match the argument types and return type in the signature of the single abstract method in the interface. This is called being compatible with the method signature. The lambda expression is thus the implementation of the interface method, and can also be assigned to a reference of that interface type.

As a demonstration, Example 1-3 shows the lambda assigned to a variable.

Example 1-3. Assigning a lambda expression to a variable
Runnable r = () -> System.out.println(
    "lambda expression implementing the run method");
new Thread(r).start();
Note

There is no class in the Java library called Lambda. Lambda expressions can only be assigned to functional interface references.

Assigning a lambda to the functional interface is the same as saying the lambda is the implementation of the single abstract method inside it. You can think of the lambda as the body of an anonymous inner class that implements the interface. That is why the lambda must be compatible with the abstract method; its argument types and return type must match the signature of that method. Notably, however, the name of the method being implemented is not important. It does not appear anywhere as part of the lambda expression syntax.

This example was especially simple because the run method takes no arguments and returns void. Consider instead the functional interface java.io.Filename​Filter, which again has been part of the Java standard library since version 1.0. Instances of Filename​Filter are used as arguments to the File.list method to restrict the returned files to only those that satisfy the method.

From the Javadocs, the FilenameFilter class contains the single abstract method accept, with the following signature:

boolean accept(File dir, String name)

The File argument is the directory in which the file is found, and the String name is the name of the file.

The code in Example 1-4 implements FilenameFilter using an anonymous inner class to return only Java source files.

Example 1-4. An anonymous inner class implementation of FilenameFilter
File directory = new File("./src/main/java");

String[] names = directory.list(new FilenameFilter() {  1
    @Override
    public boolean accept(File dir, String name) {
        return name.endsWith(".java");
    }
});
System.out.println(Arrays.asList(names));
1

Anonymous inner class

In this case, the accept method returns true if the filename ends with .java and false otherwise.

The lambda expression version is shown in Example 1-5.

Example 1-5. Lambda expression implementing FilenameFilter
File directory = new File("./src/main/java");

String[] names = directory.list((dir, name) -> name.endsWith(".java")); 1
    System.out.println(Arrays.asList(names));
}
1

Lambda expression

The resulting code is much simpler. This time the arguments are contained within parentheses, but do not have types declared. At compile time, the compiler knows that the list method takes an argument of type FilenameFilter, and therefore knows the signature of its single abstract method (accept). It therefore knows that the arguments to accept are a File and a String, so that the compatible lambda expression arguments must match those types. The return type on accept is a boolean, so the expression to the right of the arrow must also return a boolean.

If you wish to specify the data types in the code, you are free to do so, as in Example 1-6.

Example 1-6. Lambda expression with explicit data types
File directory = new File("./src/main/java");

String[] names = directory.list((File dir, String name) -> 1
    name.endsWith(".java"));
1

Explicit data types

Finally, if the implementation of the lambda requires more than one line, you need to use braces and an explicit return statement, as shown in Example 1-7.

Example 1-7. A block lambda
File directory = new File("./src/main/java");

String[] names = directory.list((File dir, String name) -> {  1
    return name.endsWith(".java");
});
System.out.println(Arrays.asList(names));
1

Block syntax

This is known as a block lambda. In this case the body still consists of a single line, but the braces now allow for multiple statements. The return keyword is now required.

Lambda expressions never exist alone. There is always a context for the expression, which indicates the functional interface to which the expression is assigned. A lambda can be an argument to a method, a return type from a method, or assigned to a reference. In each case, the type of the assignment must be a functional interface.

1.2 Method References

Problem

You want to use a method reference to access an existing method and treat it like a lambda expression.

Solution

Use the double-colon notation to separate an instance reference or class name from the method.(((”

(double colon) notation in method references”)))

Discussion

If a lambda expression is essentially treating a method as though it was a object, then a method reference treats an existing method as though it was a lambda.

For example, the forEach method in Iterable takes a Consumer as an argument. Example 1-8 shows that the Consumer can be implemented as either a lambda expression or as a method reference.

Example 1-8. Using a method reference to access println
Stream.of(3, 1, 4, 1, 5, 9)
        .forEach(x -> System.out.println(x));     1

Stream.of(3, 1, 4, 1, 5, 9)
        .forEach(System.out::println);            2

Consumer<Integer> printer = System.out::println;  3
Stream.of(3, 1, 4, 1, 5, 9)
        .forEach(printer);
1

Using a lambda expression

2

Using a method reference

3

Assigning the method reference to a functional interface

The double-colon notation provides the reference to the println method on the Sys⁠tem.out instance, which is a reference of type PrintStream. No parentheses are placed at the end of the method reference. In the example shown, each element of the stream is printed to standard output.2

Tip

If you write a lambda expression that consists of one line that invokes a method, consider using the equivalent method reference instead.

The method reference provides a couple of (minor) advantages over the lambda syntax. First, it tends to be shorter, and second, it often includes the name of the class containing the method. Both make the code easier to read.

Method references can be used with static methods as well, as shown in Example 1-9.

Example 1-9. Using a method reference to a static method
Stream.generate(Math::random)          1
        .limit(10)
        .forEach(System.out::println); 2
1

Static method

2

Instance method

The generate method on Stream takes a Supplier as an argument, which is a functional interface whose single abstract method takes no arguments and produces a single result. The random method in the Math class is compatible with that signature, because it also takes no arguments and produces a single, uniformly distributed, pseudorandom double between 0 and 1. The method reference Math::random refers to that method as the implementation of the Supplier interface.

Since Stream.generate produces an infinite stream, the limit method is used to ensure only 10 values are produced, which are then printed to standard output using the System.out::println method reference as an implementation of Consumer.

Syntax

There are three forms of the method reference syntax, and one is a bit misleading:

object::instanceMethod

Refer to an instance method using a reference to the supplied object, as in Syste⁠m.out::println

Class::staticMethod

Refer to static method, as in Math::max

Class::instanceMethod

Invoke the instance method on a reference to an object supplied by the context, as in String::length

That last example is the confusing one, because as Java developers we’re accustomed to seeing only static methods invoked via a class name. Remember that lambda expressions and method references never exist in a vacuum—there’s always a context. In the case of an object reference, the context will supply the argument(s) to the method. In the printing case, the equivalent lambda expression is (as shown in context in Example 1-8):

// equivalent to System.out::println
x -> System.out.println(x)

The context provides the value of x, which is used as the method argument.

The situation is similar for the static max method:

// equivalent to Math::max
(x,y) -> Math.max(x,y)

Now the context needs to supply two arguments, and the lambda returns the greater one.

The “instance method through the class name” syntax is interpreted differently. The equivalent lambda is:

// equivalent to String::length
x -> x.length()

This time, when the context provides x, it is used as the target of the method, rather than as an argument.

Tip

If you refer to a method that takes multiple arguments via the class name, the first element supplied by the context becomes the target and the remaining elements are arguments to the method.

Example 1-10 shows the sample code.

Example 1-10. Invoking a multiple-argument instance method from a class reference
List<String> strings =
    Arrays.asList("this", "is", "a", "list", "of", "strings");
List<String> sorted = strings.stream()
        .sorted((s1, s2) -> s1.compareTo(s2))  1
        .collect(Collectors.toList());

List<String> sorted = strings.stream()
        .sorted(String::compareTo)             1
        .collect(Collectors.toList());
1

Method reference and equivalent lambda

The sorted method on Stream takes a Comparator<T> as an argument, whose single abstract method is int compare(String other). The sorted method supplies each pair of strings to the comparator and sorts them based on the sign of the returned integer. In this case, the context is a pair of strings. The method reference syntax, using the class name String, invokes the compareTo method on the first element (s1 in the lambda expression) and uses the second element s2 as the argument to the method.

In stream processing, you frequently access an instance method using the class name in a method reference if you are processing a series of inputs. The code in Example 1-11 shows the invocation of the length method on each individual String in the stream.

Example 1-11. Invoking the length method on String using a method reference
Stream.of("this", "is", "a", "stream", "of", "strings")
        .map(String::length)            1
        .forEach(System.out::println);  2
1

Instance method via class name

2

Instance method via object reference

This example transforms each string into an integer by invoking the length method, then prints each result.

A method reference is essentially an abbreviated syntax for a lambda. Lambda expressions are more general, in that each method reference has an equivalent lambda expression but not vice versa. The equivalent lambdas for the method references from Example 1-11 are shown in Example 1-12.

Example 1-12. Lambda expression equivalents for method references
Stream.of("this", "is", "a", "stream", "of", "strings")
        .map(s -> s.length())
        .forEach(x -> System.out.println(x));

As with any lambda expression, the context matters. You can also use this or super as the left side of a method reference if there is any ambiguity.

See Also

You can also invoke constructors using the method reference syntax. Constructor references are shown in Recipe 1.3. The package of functional interfaces, including the Supplier interface discussed in this recipe, is covered in Chapter 2.

1.3 Constructor References

Problem

You want to instantiate an object using a method reference as part of a stream pipeline.

Solution

Use the new keyword as part of a method reference.

Discussion

When people talk about the new syntax added to Java 8, they mention lambda expressions, method references, and streams. For example, say you had a list of people and you wanted to convert it to a list of names. One way to do so would be the snippet shown in Example 1-13.

Example 1-13. Converting a list of people to a list of names
List<String> names = people.stream()
    .map(person -> person.getName()) 1
    .collect(Collectors.toList());

// or, alternatively,

List<String> names = people.stream()
    .map(Person::getName)           2
    .collect(Collectors.toList());
1

Lambda expression

2

Method reference

What if you want to go the other way? What if you have a list of strings and you want to create a list of Person references from it? In that case you can use a method reference, but this time using the keyword new. That syntax is called a constructor reference.

To show how it is used, start with a Person class, which is just about the simplest Plain Old Java Object (POJO) imaginable. All it does is wrap a simple string attribute called name in Example 1-14.

Example 1-14. A Person class
public class Person {
    private String name;

    public Person() {}

    public Person(String name) {
        this.name = name;
    }

    // getters and setters ...

    // equals, hashCode, and toString methods ...
}

Given a collection of strings, you can map each one into a Person using either a lambda expression or the constructor reference in Example 1-15.

Example 1-15. Transforming strings into Person instances
List<String> names =
    Arrays.asList("Grace Hopper", "Barbara Liskov", "Ada Lovelace",
        "Karen Spärck Jones");

List<Person> people = names.stream()
    .map(name -> new Person(name)) 1
    .collect(Collectors.toList());

// or, alternatively,

List<Person> people = names.stream()
    .map(Person::new)              2
    .collect(Collectors.toList());
1

Using a lambda expression to invoke the constructor

2

Using a constructor reference instantiating Person

The syntax Person::new refers to the constructor in the Person class. As with all lambda expressions, the context determines which constructor is executed. Because the context supplies a string, the one-arg String constructor is used.

Copy constructor

A copy constructor takes a Person argument and returns a new Person with the same attributes, as shown in Example 1-16.

Example 1-16. A copy constructor for Person
public Person(Person p) {
    this.name = p.name;
}

This is useful if you want to isolate streaming code from the original instances. For example, if you already have a list of people, convert the list into a stream, and then back into a list, the references are the same (see Example 1-17).

Example 1-17. Converting a list to a stream and back
Person before = new Person("Grace Hopper");

List<Person> people = Stream.of(before)
    .collect(Collectors.toList());
Person after = people.get(0);

assertTrue(before == after);                          1

before.setName("Grace Murray Hopper");                2
assertEquals("Grace Murray Hopper", after.getName()); 3
1

Same object

2

Change name using before reference

3

Name has changed in the after reference

Using a copy constructor, you can break that connection, as in Example 1-18.

Example 1-18. Using the copy constructor
people = Stream.of(before)
      .map(Person::new)            1
      .collect(Collectors.toList());
after = people.get(0);
assertFalse(before == after);      2
assertEquals(before, after);       3

before.setName("Rear Admiral Dr. Grace Murray Hopper");
assertFalse(before.equals(after));
1

Use copy constructor

2

Different objects

3

But equivalent

This time, when invoking the map method, the context is a stream of Person instances. Therefore the Person::new syntax invokes the constructor that takes a Person and returns a new, but equivalent, instance, and has broken the connection between the before reference and the after reference.3

Varargs constructor

Consider now a varargs constructor added to the Person POJO, shown in Example 1-19.

Example 1-19. A Person constructor that takes a variable argument list of String
public Person(String... names) {
    this.name = Arrays.stream(names)
                      .collect(Collectors.joining(" "));
}

This constructor takes zero or more string arguments and concatenates them together with a single space as the delimiter.

How can that constructor get invoked? Any client that passes zero or more string arguments separated by commas will call it. One way to do that is to take advantage of the split method on String that takes a delimiter and returns a String array:

String[] split(String delimiter)

Therefore, the code in Example 1-20 splits each string in the list into individual words and invokes the varargs constructor.

Example 1-20. Using the varargs constructor
names.stream()                     1
    .map(name -> name.split(" "))  2
    .map(Person::new)              3
    .collect(Collectors.toList()); 4
1

Create a stream of strings

2

Map to a stream of string arrays

3

Map to a stream of Person

4

Collect to a list of Person

This time, the context for the map method that contains the Person::new constructor reference is a stream of string arrays, so the varargs constructor is called. If you add a simple print statement to that constructor:

System.out.println("Varargs ctor, names=" + Arrays.asList(names));

then the result is:

Varargs ctor, names=[Grace, Hopper]
Varargs ctor, names=[Barbara, Liskov]
Varargs ctor, names=[Ada, Lovelace]
Varargs ctor, names=[Karen, Spärck, Jones]

Arrays

Constructor references can also be used with arrays. If you want an array of Person instances, Person[], instead of a list, you can use the toArray method on Stream, whose signature is:

<A> A[] toArray(IntFunction<A[]> generator)

This method uses A to represent the generic type of the array returned containing the elements of the stream, which is created using the provided generator function. The cool part is that a constructor reference can be used for that, too, as in Example 1-21.

Example 1-21. Creating an array of Person references
Person[] people = names.stream()
    .map(Person::new)         1
    .toArray(Person[]::new);  2
1

Constructor reference for Person

2

Constructor reference for an array of Person

The toArray method argument creates an array of Person references of the proper size and populates it with the instantiated Person instances.

Constructor references are just method references by another name, using the word new to invoke a constructor. Which constructor is determined by the context, as usual. This technique gives a lot of flexibility when processing streams.

See Also

Method references are discussed in Recipe 1.2.

1.4 Functional Interfaces

Problem

You want to use an existing functional interface, or write your own.

Solution

Create an interface with a single, abstract method, and add the @FunctionalInterface annotation.

Discussion

A functional interface in Java 8 is an interface with a single, abstract method. As such, it can be the target for a lambda expression or method reference.

The use of the term abstract here is significant. Prior to Java 8, all methods in interfaces were considered abstract by default—you didn’t even need to add the keyword.

For example, here is the definition of an interface called PalindromeChecker, shown in Example 1-22.

Example 1-22. A Palindrome Checker interface
@FunctionalInterface
public interface PalindromeChecker {
    boolean isPalidrome(String s);
}

All methods in an interface are public,4 so you can leave out the access modifier, just as you can leave out the abstract keyword.

Since this interface has only a single, abstract method, it is a functional interface. Java 8 provides an annotation called @FunctionalInterface in the java.lang package that can be applied to the interface, as shown in the example.

This annotation is not required, but is a good idea, for two reasons. First, it triggers a compile-time check that the interface does, in fact, satisfy the requirement. If the interface has either zero abstract methods or more than one, you will get a compiler error.

The other benefit to adding the @FunctionalInterface annotation is that it generates a statement in the Javadocs as follows:

Functional Interface:
This is a functional interface and can therefore be used as the assignment
target for a lambda expression or method reference.

Functional interfaces can have default and static methods as well. Both default and static methods have implementations, so they don’t count against the single abstract method requirement. Example 1-23 shows the sample code.

Example 1-23. MyInterface is a functional interface with static and default methods
@FunctionalInterface
public interface MyInterface {
    int myMethod();          1
    // int myOtherMethod();  2

    default String sayHello() {
        return "Hello, World!";
    }

    static void myStaticMethod() {
        System.out.println("I'm a static method in an interface");
    }
}
1

Single abstract method

2

If added, this would no longer be a functional interface

Note that if the commented method myOtherMethod was included, the interface would no longer satisfy the functional interface requirement. The annotation would generate an error of the form “multiple non-overriding abstract methods found.”

Interfaces can extend other interfaces, even more than one. The annotation checks the current interface. So if one interface extends an existing functional interface and adds another abstract method, it is not itself a functional interface. See Example 1-24.

Example 1-24. Extending a functional interface—no longer functional
public interface MyChildInterface extends MyInterface {
    int anotherMethod(); 1
}
1

Additional abstract method

The MyChildInterface is not a functional interface, because it has two abstract methods: myMethod, which it inherits from MyInterface; and anotherMethod, which it declares. Without the @FunctionalInterface annotation, this compiles, because it’s a standard interface. It cannot, however, be the target of a lambda expression.

One edge case should also be noted. The Comparator interface is used for sorting, which is discussed in other recipes. If you look at the Javadocs for that interface and select the Abstract Methods tab, you see the methods shown in Figure 1-1.

mjr 0101
Figure 1-1. Abstract methods in the Comparator class

Wait, what? How can this be a functional interface if there are two abstract methods, especially if one of them is actually implemented in java.lang.Object?

What is special here is that the equals method shown is from Object, and therefore already has a default implementation. The detailed documentation says that for performance reasons you can supply your own equals method that satisfies the same contract, but that “it is always safe not (emphasis in original) to override” this method.

The rules for functional interfaces say that methods from Object don’t count against the single abstract method limit, so Comparator is still a functional interface.

See Also

Default methods in interfaces are discussed in Recipe 1.5, and static methods in interfaces are discussed in Recipe 1.6.

1.5 Default Methods in Interfaces

Problem

You want to provide an implementation of a method inside an interface.

Solution

Use the keyword default on the interface method, and add the implementation in the normal way.

Discussion

The traditional reason Java never supported multiple inheritance is the so-called diamond problem. Say you have an inheritance hierarchy as shown in the (vaguely UML-like) Figure 1-2.

mjr 0102
Figure 1-2. Animal inheritance

Class Animal has two child classes, Bird and Horse, each of which overrides the speak method from Animal, in Horse to say “whinny” and in Bird to say “chirp.” What, then, does Pegasus (which multiply inherits from both Horse and Bird)5 say? What if you have a reference of type Animal assigned to an instance of Pegasus? What then should the speak method return?

Animal animal = new Pegaus();
animal.speak(); // whinny, chirp, or other?

Different languages take different approaches to this problem. In C++, for example, multiple inheritance is allowed, but if a class inherits conflicting implementations, it won’t compile.6 In Eiffel,7 the compiler allows you to choose which implementation you want.

Java’s approach was to prohibit multiple inheritance, and interfaces were introduced as a workaround for when a class has an “is a kind of” relationship with more than one type. Since interfaces had only abstract methods, there were no implementations to conflict. Multiple inheritance is allowed with interfaces, but again that works because only the method signatures are inherited.

The problem is, if you can never implement a method in an interface, you wind up with some awkward designs. Among the methods in the java.util.Collection interface, for example, are:

boolean isEmpty()
int     size()

The isEmpty method returns true if there are no elements in the collection, and false otherwise. The size method returns the number of elements in the collections. Regardless of the underlying implementation, you can immediately implement the isEmpty method in terms of size, as in Example 1-25.

Example 1-25. Implementation of isEmpty in terms of size
public boolean isEmpty() {
    return size() == 0;
}

Since Collection is an interface, you can’t do this in the interface itself. Instead, the standard library includes an abstract class called java.util.AbstractCollection, which includes, among other code, exactly the implementation of isEmpty shown here. If you are creating your own collection implementation and you don’t already have a superclass, you can extend AbstractCollection and you get the isEmpty method for free. If you already have a superclass, you have to implement the Col⁠lection interface instead and remember to provide your own implementation of isEmpty as well as size.

All of this is quite familiar to experienced Java developers, but as of Java 8 the situation changes. Now you can add implementations to interface methods. All you have to do is add the keyword default to a method and provide an implementation. The code in Example 1-26 shows an interface with both abstract and default methods.

Example 1-26. An Employee interface with a default method
public interface Employee {
    String getFirst();

    String getLast();

    void convertCaffeineToCodeForMoney();

    default String getName() {  1
        return String.format("%s %s", getFirst(), getLast());
    }
}
1

Default method with an implementation

The getName method has the keyword default, and its implementation is in terms of the other, abstract, methods in the interface, getFirst and getLast.

Many of the existing interfaces in Java have been enhanced with default methods in order to maintain backward compatibility. Normally when you add a new method to an interface, you break all the existing implementations. By adding a new method as a default, all the existing implementations inherit the new method and still work. This allowed the library maintainers to add new default methods throughout the JDK without breaking existing implementations.

For example, java.util.Collection now contains the following default methods:

default boolean        removeIf(Predicate<? super E> filter)
default Stream<E>      stream()
default Stream<E>      parallelStream()
default Spliterator<E> spliterator()

The removeIf method removes all of the elements from the collection that satisfy the Predicate8 argument, returning true if any elements were removed. The stream and parallelStream methods are factory methods for creating streams. The spliterator method returns an object from a class that implements the Spliterator interface, which is an object for traversing and partitioning elements from a source.

Default methods are used the same way any other methods are used, as Example 1-27 shows.

Example 1-27. Using default methods
List<Integer> nums = new ArrayList<>();
nums.add(-3); nums.add(1); nums.add(4);
nums.add(-1); nums.add(5); nums.add(9);
boolean removed = nums.removeIf(n -> n <= 0);  1
System.out.println("Elements were " + (removed ? "" : "NOT") + " removed");
nums.forEach(System.out::println);             2
1

Use the default method removeIf from Collection

2

Use the default method forEach from Iterator

What happens when a class implements two interfaces with the same default method? That is the subject of Recipe 5.5, but the short answer is that if the class implements the method itself everything is fine. See Recipe 5.5 for details.

See Also

Recipe 5.5 shows the rules that apply when a class implements multiple interfaces with default methods.

1.6 Static Methods in Interfaces

Problem

You want to add a class-level utility method to an interface, along with an implementation.

Solution

Make the method static and provide the implementation in the usual way.

Discussion

Static members of Java classes are class-level, meaning they are associated with the class as a whole rather than with a particular instance. That makes their use in interfaces problematic from a design point of view. Some questions include:

  • What does a class-level member mean when the interface is implemented by many different classes?

  • Does a class need to implement an interface in order to use a static method?

  • Static methods in classes are accessed by the class name. If a class implements an interface, does a static method get called from the class name or the interface name?

The designers of Java could have decided these questions in several different ways. Prior to Java 8, the decision was not to allow static members in interfaces at all.

Unfortunately, however, that led to the creation of utility classes: classes that contain only static methods. A typical example is java.util.Collections, which contains methods for sorting and searching, wrapping collections in synchronized or unmodifiable types, and more. In the NIO package, java.nio.file.Paths is another example. It contains only static methods that parse Path instances from strings or URIs.

Now, in Java 8, you can add static methods to interfaces whenever you like. The requirements are:

  • Add the static keyword to the method.

  • Provide an implementation (which cannot be overridden). In this way they are like default methods, and are included in the default tab in the Javadocs.

  • Access the method using the interface name. Classes do not need to implement an interface to use its static methods.

One example of a convenient static method in an interface is the comparing method in java.util.Comparator, along with its primitive variants, comparingInt, compa⁠ringLong, and comparingDouble. The Comparator interface also has static methods naturalOrder and reverseOrder. Example 1-28 shows how they are used.

Example 1-28. Sorting strings
List<String> bonds = Arrays.asList("Connery", "Lazenby", "Moore",
    "Dalton", "Brosnan", "Craig");

List<String> sorted = bonds.stream()
    .sorted(Comparator.naturalOrder())                 1
    .collect(Collectors.toList());
// [Brosnan, Connery, Craig, Dalton, Lazenby, Moore]

sorted = bonds.stream()
    .sorted(Comparator.reverseOrder())                 2
    .collect(Collectors.toList());
// [Moore, Lazenby, Dalton, Craig, Connery, Brosnan]

sorted = bonds.stream()
    .sorted(Comparator.comparing(String::toLowerCase)) 3
    .collect(Collectors.toList());
// [Brosnan, Connery, Craig, Dalton, Lazenby, Moore]

sorted = bonds.stream()
    .sorted(Comparator.comparingInt(String::length))   4
    .collect(Collectors.toList());
// [Moore, Craig, Dalton, Connery, Lazenby, Brosnan]

sorted = bonds.stream()
    .sorted(Comparator.comparingInt(String::length)    5
        .thenComparing(Comparator.naturalOrder()))
    .collect(Collectors.toList());
// [Craig, Moore, Dalton, Brosnan, Connery, Lazenby]
1

Natural order (lexicographical)

2

Reverse lexicographical

3

Sort by lowercase name

4

Sort by name length

5

Sort by length, then equal lengths lexicographically

The example shows how to use several static methods in Comparator to sort the list of actors who have played James Bond over the years.9 Comparators are discussed further in Recipe 4.1.

Static methods in interfaces remove the need to create separate utility classes, though that option is still available if a design calls for it.

The key points to remember are:

  • Static methods must have an implementation

  • You cannot override a static method

  • Call static methods from the interface name

  • You do not need to implement an interface to use its static methods

See Also

Static methods from interfaces are used throughout this book, but Recipe 4.1 covers the static methods from Comparator used here.

1 Coined by Gordon Moore, one of the co-founders of Fairchild Semiconductor and Intel, based on the observation that the number of transistors that could be packed into an integrated circuit doubled roughly every 18 months. See Wikipedia’s Moore’s law entry for details.

2 It is difficult to discuss lambdas or method references without discussing streams, which have their own chapter later. Suffice it to say that a stream produces a series of elements sequentially, does not store them anywhere, and does not modify the original source.

3 I mean no disrespect by treating Admiral Hopper as an object. I have no doubt she could still kick my butt, and she passed away in 1992.

4 At least until Java 9, when private methods are also allowed in interfaces. See Recipe 10.2 for details.

5 “A magnificent horse, with the brain of a bird.” (Disney’s Hercules movie, which is fun if you pretend you know nothing about Greek mythology and never heard of Hercules.)

6 This can be solved by using virtual inheritance, but still.

7 There’s an obscure reference for you, but Eiffel was one of the foundational languages of object-oriented programming. See Bertrand Meyer’s Object-Oriented Software Construction, Second Edition (Prentice Hall, 1997).

8 Predicate is one of the new functional interfaces in the java.util.function package, described in detail in Recipe 2.3.

9 The temptation to add Idris Elba to the list is almost overwhelming, but no such luck as yet.

Get Modern Java Recipes now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.