3

In Java 8, default methods for an interface were introduced for adding methods to existing interface without breaking backward compatibility.

Since default methods are non-abstract, they can be used to make a FunctionalInterface with multiple overridable methods.

Say, a StringTransformer interface has two methods, transform, which transforms given String, and end to free resources:

interface StringTransformer {
    String transform(String s);

    void end();
}

But some implementations may not have resources to free, so we can provide empty default method for end and use lambda function and method reference for StringTransformer:

interface StringTransformer {
    String transform(String s);

    default void end() {
    }
}

StringTransformer x = String::trim;
StringTransformer y = (x -> x + x);

Is this a valid/best practice, or an anti-pattern and abuse of default methods?

ylem
  • 157
  • 2
  • 6
  • 5
    *Opinion:* That's an abuse of default methods. --- Also, if you want a method for cleaning up resources, you should implement [`AutoCloseable`](https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html) and its [`void close()`](https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html#close--) method, allowing caller to use [try-with-resources](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html). – Andreas Apr 02 '18 at 02:11
  • 2
    You might want to read [this answer](https://stackoverflow.com/a/24617900/5221149) for why it's an abuse of default methods. Summary: *Lambda are **functions**, not objects.* – Andreas Apr 02 '18 at 02:18
  • 1
    @Andreas Seems to me it's a pretty conventional use case for default methods. You could argue that it's inappropriate to instantiate what's essentially an abstract class as a lambda, but even that's a bit of a leap from your link. – shmosel Apr 03 '18 at 01:02

3 Answers3

1

As said in this answer, allowing to create interfaces with more than one method still being functional interfaces, is one of the purposes of default methods. As also mentioned there, you will find examples within the Java API itself, say Comparator, Predicate, or Function, having default methods and intentionally being functional interfaces.

It doesn’t matter whether the default implementation is doing nothing or not, the more important question is, how natural is this default implementation. Does it feel like a kludge, just to make lambdas possible, or is it indeed what some or even most implementations would use any way (regardless of how they are implemented)?

Not needing a special clean up action might be indeed a common case, even if you follow the suggestion made in a comment, to let your interface extend AutoCloseable and name the method close instead of end. Note that likewise, Stream implements AutoCloseable and its default behavior is to do nothing on close(). You could even follow the pattern to allow specifying the cleanup action as separate Runnable, similar to Stream.onClose(Runnable):

public interface StringTransformer extends UnaryOperator<String>, AutoCloseable {
    static StringTransformer transformer(Function<String,String> f) {
        return f::apply;
    }
    String transform(String s);
    @Override default String apply(String s) { return transform(s); }
    @Override default void close() {}
    default StringTransformer onClose(Runnable r) {
        return new StringTransformer() {
            @Override public String transform(String s) {
                return StringTransformer.this.transform(s);
            }
            @Override public void close() {
                try(StringTransformer.this) { r.run(); }
            }
        };
    }
}

This allows registering a cleanup action via onClose, so the following works:

try(StringTransformer t = 
        StringTransformer.transformer(String::toUpperCase)
                         .onClose(()->System.out.println("close"))) {
    System.out.println(t.apply("some text"));
}

resp.

try(StringTransformer t = transformer(String::toUpperCase)
                         .onClose(()->System.out.println("close 1"))) {
    System.out.println(t.apply("some text"));
}

if you use import static. It also ensures safe closing if you chain multiple actions like

try(StringTransformer t = transformer(String::toUpperCase)
                         .onClose(()->System.out.println("close 1"))
                         .onClose(()->{ throw new IllegalStateException(); })) {
    System.out.println(t.apply("some text"));
}

or

try(StringTransformer t = transformer(String::toUpperCase)
                         .onClose(()->{ throw new IllegalStateException("outer fail"); })
                         .onClose(()->{ throw new IllegalStateException("inner fail"); })){
    System.out.println(t.apply("some text"));
}

Note that try(StringTransformer.this) { r.run(); } is Java 9 syntax. For Java 8, you would need try(StringTransformer toClose = StringTransformer.this) { r.run(); }.

Holger
  • 285,553
  • 42
  • 434
  • 765
0

Theoretically there is nothing wrong wit h it. You can use this type of interfaces. I would like to start with the conditions where you need this type of construct.

Backward capability

As you mentioned, default methods are introduced to maintain backward capability, so usage of default methods to make interface backward compatible is justified.

Optional implementation

Default empty methods can be used to make implementation of any method optional. However, you can make a abstract adaptor class of such interface with a default implementation. This strategy has been used for long time and it is better than default methods in interface as with abstract class, we can define complex default implementation. Moreover, we can start with a simple empty method at the beginning and change it later to have complex default implementation while in case of default methods in interface it is not possible as certain functionalities of class is not available.

In addition, Java 8 has also introduced FunctionalInterface to mark any interface as a functional interface. According to official guide, usage of lambda & method reference should be avoided in case of interfaces having one method but not annotated with FunctionalInterface.

Neel Patel
  • 358
  • 2
  • 13
0

To my mind it’s not a good idea to transform a regular interface that can be referenced as data type to functional interface.

If you do this then user can implement the StringTransform where they need actually this interface as a type and where they create lambdas. That reduces readability and maintainability.

  • A functional interface *is* a regular interface. There is nothing irregular with it. And what does “where they need actually this interface as a type” mean? – Holger Apr 03 '18 at 09:15