4
\$\begingroup\$

I am currently implementing a fun project involving lambdas and this project makes heavy use of the ability of Java 8 to have default methods in interfaces.

But when does such "heavy use" turn into an outright abuse? Here is one example of an interface which I have defined, and its 4 methods may extend to even more in the future; am I overdoing it?

@FunctionalInterface
public interface ThrowingFunction<T, R>
    extends Function<T, R>
{
    R doApply(T t)
        throws Throwable;

    @Override
    default R apply(T t)
    {
        try {
            return doApply(t);
        } catch (Error | RuntimeException e) {
            throw e;
        } catch (Throwable tooBad) {
            throw new ThrownByLambdaException(tooBad);
        }
    }

    default Function<T, R> orReturn(R defaultValue)
    {
        return t -> {
            try {
                return doApply(t);
            } catch (Error | RuntimeException e) {
                throw e;
            } catch (Throwable ignored) {
                return defaultValue;
            }
        };
    }

    default <E extends RuntimeException> Function<T, R> orThrow(
        Class<E> exceptionClass)
    {
        return t -> {
            try {
                return doApply(t);
            } catch (Error | RuntimeException e) {
                throw e;
            } catch (Throwable tooBad) {
                throw ThrowablesFactory.INSTANCE.get(exceptionClass, tooBad);
            }
        };
    }
}

(Not that this is relevant, but the source for ThrowablesFactory can be viewed here.)

\$\endgroup\$
1
\$\begingroup\$

When does such "heavy use" turn into outright abuse?

What it seems like you're asking is "Am I abusing Default Methods?".

When in doubt about how to use a new piece of functionality I typically turn to the Java tutorials. As is the case with other new pieces of functionality in Java 8, there is documentation about Default Methods here.

One specific snippet that caught my eye is this:

"Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces."

I would ask yourself several questions about the implementation in assessing whether or not the new functionality is being abused:

  1. Am I exposing too much business logic in the interface through the default methods?
  2. Is there sufficient justification to expose the methods through the interface as opposed to having them implemented in an abstract class?
  3. Is the code going to be maintainable?
  4. Am I using the default methods feature in the way which it was created to be used?

To address your question about overdoing it, in my opinion you are. The documentation seems to imply what you should be using default methods for and this does not fit that mold. Especially since you're starting with an empty slate as opposed to adding new functionality to existing libraries for which you have to maintain binary compatibility.

In addition, take a look at the Java 8 List interface. Default methods are provided which accept other functional arguments and utilize those arguments on the implemented instance. The same seems true for the Comparator interface.

From the snippet provided it seems like the default methods do not utilize functional arguments. I also don't see any advantage of doing things this way over using the template pattern (which it seems like you're doing anyway) and having an abstract base class declare the doApply method and have the abstract class call that from behind the curtain of the interface.

I'm biased in that I favor hiding functionality behind interfaces as opposed to exposing it. I perceive the intention of interfaces to be encapsulating data and hiding business logic and design following those concepts.

Here is some reading pertaining to anti-patterns that I try to keep in mind that may be applicable to your question:

Also, Oracle almost HAD to add some kind of functionality to the language that would provide backwards compatibility with existing libraries while also injecting new functionality. I consider the usage that the developers of the language chose to be a good starting place for evaluating my own usage.

As an aside, there looks to be some really cool stuff in your project.

\$\endgroup\$
  • \$\begingroup\$ On the other hand, if I were using an abstract class I wouldn't be able to do ThrowingUnaryOperator<Path> operator = Path::toRealPath;... And yeah, I am probably using default methods in a way they were not intended to be used. \$\endgroup\$ – fge Dec 29 '14 at 21:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.