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
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
(
)
{
@Override
public
void
run
(
)
{
System
.
out
.
println
(
"inside runnable using an anonymous inner class"
)
;
}
}
)
.
start
(
)
;
}
}
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.FilenameFilter
, which again has been part of the Java standard library since version 1.0. Instances of FilenameFilter
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
(
)
{
@Override
public
boolean
accept
(
File
dir
,
String
name
)
{
return
name
.
endsWith
(
".java"
)
;
}
}
)
;
System
.
out
.
println
(
Arrays
.
asList
(
names
)
)
;
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"
)
)
;
System
.
out
.
println
(
Arrays
.
asList
(
names
)
)
;
}
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
)
-
>
name
.
endsWith
(
".java"
)
)
;
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
)
-
>
{
return
name
.
endsWith
(
".java"
)
;
}
)
;
System
.
out
.
println
(
Arrays
.
asList
(
names
)
)
;
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
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
)
)
;
Stream
.
of
(
3
,
1
,
4
,
1
,
5
,
9
)
.
forEach
(
System
.
out
:
:
println
)
;
Consumer
<
Integer
>
printer
=
System
.
out
:
:
println
;
Stream
.
of
(
3
,
1
,
4
,
1
,
5
,
9
)
.
forEach
(
printer
)
;
The double-colon notation provides the reference to the println
method on the System.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
)
.
limit
(
10
)
.
forEach
(
System
.
out
:
:
println
)
;
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
System.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
)
)
.
collect
(
Collectors
.
toList
(
)
)
;
List
<
String
>
sorted
=
strings
.
stream
(
)
.
sorted
(
String:
:
compareTo
)
.
collect
(
Collectors
.
toList
(
)
)
;
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
)
.
forEach
(
System
.
out
:
:
println
)
;
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
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
(
)
)
.
collect
(
Collectors
.
toList
(
)
)
;
// or, alternatively,
List
<
String
>
names
=
people
.
stream
(
)
.
map
(
Person:
:
getName
)
.
collect
(
Collectors
.
toList
(
)
)
;
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
)
)
.
collect
(
Collectors
.
toList
(
)
)
;
// or, alternatively,
List
<
Person
>
people
=
names
.
stream
(
)
.
map
(
Person:
:
new
)
.
collect
(
Collectors
.
toList
(
)
)
;
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
)
;
before
.
setName
(
"Grace Murray Hopper"
)
;
assertEquals
(
"Grace Murray Hopper"
,
after
.
getName
(
)
)
;
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
)
.
collect
(
Collectors
.
toList
(
)
)
;
after
=
people
.
get
(
0
)
;
assertFalse
(
before
=
=
after
)
;
assertEquals
(
before
,
after
)
;
before
.
setName
(
"Rear Admiral Dr. Grace Murray Hopper"
)
;
assertFalse
(
before
.
equals
(
after
)
)
;
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
(
)
.
map
(
name
-
>
name
.
split
(
" "
)
)
.
map
(
Person:
:
new
)
.
collect
(
Collectors
.
toList
(
)
)
;
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
)
.
toArray
(
Person
[
]
:
:
new
)
;
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
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
(
)
;
// int myOtherMethod();
default
String
sayHello
(
)
{
return
"Hello, World!"
;
}
static
void
myStaticMethod
(
)
{
System
.
out
.
println
(
"I'm a static method in an 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
(
)
;
}
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.
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
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.
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 Collection
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
(
)
{
return
String
.
format
(
"%s %s"
,
getFirst
(
)
,
getLast
(
)
)
;
}
}
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 Predicate
8 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
)
;
System
.
out
.
println
(
"Elements were "
+
(
removed
?
""
:
"NOT"
)
+
" removed"
)
;
nums
.
forEach
(
System
.
out
:
:
println
)
;
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
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
, comparingLong
, 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
(
)
)
.
collect
(
Collectors
.
toList
(
)
)
;
// [Brosnan, Connery, Craig, Dalton, Lazenby, Moore]
sorted
=
bonds
.
stream
(
)
.
sorted
(
Comparator
.
reverseOrder
(
)
)
.
collect
(
Collectors
.
toList
(
)
)
;
// [Moore, Lazenby, Dalton, Craig, Connery, Brosnan]
sorted
=
bonds
.
stream
(
)
.
sorted
(
Comparator
.
comparing
(
String:
:
toLowerCase
)
)
.
collect
(
Collectors
.
toList
(
)
)
;
// [Brosnan, Connery, Craig, Dalton, Lazenby, Moore]
sorted
=
bonds
.
stream
(
)
.
sorted
(
Comparator
.
comparingInt
(
String:
:
length
)
)
.
collect
(
Collectors
.
toList
(
)
)
;
// [Moore, Craig, Dalton, Connery, Lazenby, Brosnan]
sorted
=
bonds
.
stream
(
)
.
sorted
(
Comparator
.
comparingInt
(
String:
:
length
)
.
thenComparing
(
Comparator
.
naturalOrder
(
)
)
)
.
collect
(
Collectors
.
toList
(
)
)
;
// [Craig, Moore, Dalton, Brosnan, Connery, Lazenby]
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.