Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Monday, April 18, 2016

Default Methods in Java Interfaces

This blog post demonstrates a new Java language feature for interfaces called default methods.  Prior to Java 8, Java interfaces have been contracts help implementing classes conform to a protocol.  In Java 8, you can now pack functionality into interfaces using default methods.  This makes the interface construct more like an abstract class, but without the restrictions of the extends keyword which limits a subclass to only one extends relationship.


Consider the following requirement: add logging to a set of unrelated classes without using delegation.  One implementation could be to define a thoroughly generic super class, say "BaseObject", and add a log() method which pulls in the correct logging configuration, isolates the logging implementation, and formats the message consistently.  This has the big downside of coupling a group of unrelated or loosely-related items and may not even be feasible of the classes are already extending something else.

With interface default methods, you can add log() method to the interface.  Implementing classes will be able to call this method as though it were made available via an extends relationship.  This means that you don't have to work against existing class hierarchies.  Also, while I mentioned that the classes being discussed were unrelated, they do have the common feature of needing the shared functionality.

This Loggable interface should be implemented by anyone needing a properly formatted log() message.

public interface Loggable {
 default void log(String message) {
  LocalDateTime d = LocalDateTime.now();
  String d_s = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(d);
  System.out.println( "[" + d_s + "] " + 
    getClass().getName() + " " + message);
 }
}

The interface has a single method flagged with the keyword "default".

I have 3 classes that are going to implement Loggable: a model (MyModel), a view (MyView), and a controller (MyController).  Although they work together as part of a fake UI framework, they're unrelated in the sense that they're likely to involve totally different class hierarchies and libraries.  For example, "MyView" might extend the JavaFX VBox class and MyModel might extend something like "AbstractDAO".  All three classes will need logging, so it's suitable for them all to implement Loggable which will not interfere with any extends relationships.

Here is the code for MyModel

public class MyModel implements Loggable {

 public void loadData(Consumer<List<MyRecord> > onFinished) {
  log("loading data");
  if( onFinished != null ) {
   List<MyRecord> recordList = new ArrayList> >();
   recordList.add( new MyRecord(1L, "hello, world"));
   recordList.add( new MyRecord(2L, "maryland"));
   recordList.add( new MyRecord(3L, "bye"));
   onFinished.accept( recordList );
  }
 }
}

Here is the code for MyView

public class MyView implements Loggable {

 public void show() {
  log("showing");
 }
 
 public void fillTable(List<MyRecord> recordList) {
  log("filling table; recordList empty?=" + (recordList == null || recordList.isEmpty()) );
  recordList.stream().forEachOrdered(System.out::println);
 }
}

Here is the code for MyController

public class MyController implements Loggable {

 private final MyView view;
 private final MyModel model;

 public MyController(MyView view, MyModel model) {
  super();
  this.view = view;
  this.model = model;
 }
 
 public void show() {
  log("showing");  
  model.loadData( (recList) -> {
   view.show();
   view.fillTable(recList);
  });
 }
}

MyRecord is a transfer object

public class MyRecord {

 private final Long id;
 private final String data;
 public MyRecord(Long id, String data) {
  super();
  this.id = id;
  this.data = data;
 }
 public Long getId() {
  return id;
 }
 public String getData() {
  return data;
 }
 @Override
 public String toString() {
  return "MyRecord [id=" + id + ", data=" + data + "]";
 }
  
}

And the program is called from a main()

public static void main(String[] args) {

 new MyController(
   new MyView(),
   new MyModel()
   ).show();
}

Default methods in interfaces give you the ability to add functionality to classes without breaking a class hierarchy.  Now, there is a third way to add functionality to a class in addition to extends and delegation.  The default method is not a substitute for class inheritance -- there is no super() construct -- however it can bring your object-oriented code closer to the multiple inheritance paradigm.

No comments:

Post a Comment