Today, the topic is not about the Lambda expression -- is about the other change to the language in Java 8: default methods. In the past, the Java interface can only declare methods, but can not provide implementation (or called define methods). Therefore, in most design, static utility methods are defined in a class. Basically, that is not a big problem. However, someone may think about that should I define a private default constructor for the class?
In addition, Java does not support multiple inheritance, but supports multiple interface implementations. Thus, in the Java library, there is a commonly seen pattern: an interface that declares the required methods, an abstract class that provides the common implementation, and a default class that provides a completed implementation. For example,
TableModel declares the minimum method set required by
AbstractTableModel provides some common implementation (e.g., the management of listeners), and
DefaultTableModel provides a completed implementation. This pattern may give rise to a problem: if class A has inherited class B, and wants to use (inherit) the abstract class of the interface C to reuse the implementation of C. However, Java does not support multiple inheritance, the only way is that A implements C by itself, but, to a certain extent, that will produce duplicated code.
During the design of Comic Surfer, in order to keep the binary compatibility between the main program and the plug-ins, I used the pattern mentioned above. In the public plug-in SDK, both interfaces and abstract classes are provided. However, in the public developer guide, I encourage the developers to inherit abstract classes to develop the plug-in. In that design, when I want to add methods into existing interfaces, I also add default implementation of new added methods into the abstract classes. Therefore, the plug-in can still run with the new version of Comic Surfer without any revising -- JVM will not complain that the class does not provide the required implementation of some methods.
For the above issues, the default methods can provide a pretty good solution. First, it is no need to define the utility methods in a class anymore. As shown in Code List 1, the implementation of static methods can be defined in an interface directly.
In addition, to a certain extent, if no member data is inside an abstract class, the role of abstract classes can be replaced. Assume that an interface is required for temperature sensors. The
TemperatureSensor interface can be desinged like Code List 2: two default methods
getTemperatureInFahrenheit() are provided to convert the Kelvin scale to Celsius scale and Fahrenheit scale, respectively. I knew that the design example is not good enough because the conversion can be extracted into independent classes, however, the simplification is appropriate to show the usage of default methods. Since the implementation has be defined in the interface, the abstract class
AbstractTemperatureSensor as shown in Code List 3 is not needed anymore.
No need of the abstract class provides some flexibility in design. For example, trying to write an adapter for a real temperature sensor device (
AbstractDeviceControl), if default methods are not supported, due to the single inheritance, the developer needs consider that let the adapter inherit whether
AbstractTemperatureSensor? Of course, composition over inheritance, but, sometimes, developers need to write more codes. Now, just like Code List 4, the adapter can obtain the implementation from the interface, and the only one chance to inherit a class is left to other designs.
Then, while adding new methods into an existing interface, the default methods in the interface can provide the backward compatibility. For example, in Code List 5, a conversion to a new scale, called Rankine scale, is added into the
TemperatureSensor interface with the default implementation. And,
TemperatureSensorDevice can be used directly without any modification.
An interface can be extended to a new interface. For example, in some countries, people are used to Fahrenheit scale and many devices only provide the value in Fahrenheit scale. For that, like Code List 6, a new interface
FahrenheitTemperatureSensor extends the
TemperatureSensor interface. In the extension, the
abstract modifier is used to redeclare the
getTemperatureInFahrenheit() method that has default implementation as abstract method. A default method can also be overridden, e.g.,
getTemperatureInCelsius() is overridden to convert the Celsius scale from the Fahrenheit scale directly, saving the conversion between Kelvin scale and Fahrenheit scale.
In fact, many developers discussed a lot about default methods because with both default methods and multiple interface implementations can provide some kind of multiple inheritance like C++ -- except that the member data are not inherited. Good or bad? It depends on how to use. I think default methods helpful. If default methods are not only available in Java 8, I really would like to revise the plug-in interfaces and utility classes with default methods in my Comic Surfer.