Java SE 8’s New Language Features, Part 1: Interface Default/Static Methods and Lambda Expressions
- Interface Default and Static Methods
- Lambda Expressions and Functional Interfaces
Java 8 has arrived and offers exciting new language features for you to learn. In this article, the first in a two-part series that introduces you to these features, I cover interface default and static methods followed by lambda expressions and functional interfaces.
I developed this article's applications with the 64-bit version of JDK 8 build 132 on a Windows 7 platform. You can download the code from this article here.
Interface Default and Static Methods
Java's interface language feature lets you declare interfaces with abstract methods and provide implementations of those methods in the classes that implement the interfaces. You are required to implement each method, which is burdensome when there are many methods to implement. Also, after publishing the interface you cannot add new abstract methods to it without breaking source and binary compatibility.
Java 8 addresses these problems by evolving the interface to support default and static methods. A default method is an instance method defined in an interface whose method header begins with the default keyword; it also provides a code body. Every class that implements the interface inherits the interface's default methods and can override them. Consider Listing 1.
Listing 1 Addressable.java.
public interface Addressable { String getStreet(); String getCity(); default String getFullAddress() { return getStreet()+", "+getCity(); } }
Listing 1 declares an Addressable interface type that could be implemented by Letter, Postcard, Parcel, and similar classes. Every Addressable instance must implement the getStreet() and getCity() methods to return street and city names. A default getFullAddress() method is provided for conveniently returning the full address, as demonstrated in Listing 2.
Listing 2 Letter.java.
public class Letter implements Addressable { private String street; private String city; public Letter(String street, String city) { this.street = street; this.city = city; } @Override public String getCity() { return city; } @Override public String getStreet() { return street; } public static void main(String[] args) { // Test the Letter class. Letter l = new Letter("123 AnyStreet", "AnyCity"); System.out.println(l.getFullAddress()); } }
Although getCity() and getStreet() must be implemented, it isn't necessary to implement getFullAddress(). However, you are allowed to override this method when necessary, and you can even re-declare it as an abstract method in an abstract class, forcing it to be implemented in concrete subclasses.
Compile Listings 1 and 2 as follows:
javac Letter.java
Run the Letter application as follows:
java Letter
You should observe the following output:
123 AnyStreet, AnyCity
Default methods have two important use cases:
- Evolving existing interfaces. To implement the new Streams API, it was necessary to evolve the Collections Framework's java.util.Collection interface by adding new default Stream<E> stream() and default Stream<E> parallelStream() methods. Without default methods, Collection implementers such as the java.util.ArrayList class would have been forced to implement these new methods or break source/binary compatibility.
- Increasing design flexibility. Abstract classes have traditionally been used to share functionality between various concrete subclasses. However, single-class extension has limited this design choice. Default methods offer greater flexibility because you can implement an interface at any point in the class hierarchy and access the interface's default methods from the implementing classes. Also, it's no longer necessary to create adapter classes for multi-method event listener interfaces. Instead, you can add a default method for each listener method to the event listener interface and override these methods as necessary.
A static method is a method that's associated with the class in which it's defined, rather than with any object created from that class. Every instance of the class shares the static methods of the class. Java 8 also lets static methods be defined in interfaces where they can assist default methods. For example, the java.util.Comparator interface defines the following static method:
static <T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor)
As well as being directly invocable, comparingDouble() is invoked from this default method of Comparator:
default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor)
When you implement an interface that contains a static method, the static method is still part of the interface and not part of the implementing class. For this reason, you cannot prefix the method with the class name. Instead, you must prefix the method with the interface name, which I demonstrate in Listing 3.
Listing 3 Z.java.
interface X { static void foo() { System.out.println("foo"); } } class Y implements X { } public class Z { public static void main(String[] args) { X.foo(); // Y.foo(); // won't compile } }
Expression Y.foo() will not compile because foo() is a static member of interface X and not a static member of class Y.
Compile Listing 3 as follows:
javac Z.java
Run the Z application as follows:
java Z
You should observe the following output:
foo
Before Java 8 made it possible to declare static methods in interfaces, it was common practice to place these methods in companion utility classes. For example, the java.util.Collections class is a companion to the java.util.Collection interface, and declares static methods that would be more appropriate in the relevant Java Collections Framework interfaces.
For example, the Collections class declares a static <T> Collection<T> synchronizedCollection(Collection<T> c) method that could be declared in the Collection interface. Similarly, Collections declares a static <T> Set<T> singleton(T o) method that would be a more appropriate member of the java.util.Set interface. Instead of having to specify Collections.synchronizedCollection(...) and Collections.singleton(...), you could specify Collection.synchronizedCollection(...) and Set.singleton(...), and it would be clear that these methods are returning a Collection and a Set, respectively.
Although these and similar changes will probably never be made to the Java Collections Framework (too much legacy code depends on the current placement of such methods), you no longer need to provide your own companion utility classes. Instead, you can place static methods in the appropriate interfaces, which is a good habit to cultivate.