When should I create my own @FunctionalInterface rather than reuse the interfaces defined in java.util.function?

softwareengineering.stackexchange https://softwareengineering.stackexchange.com/questions/347612

Question

The functional interfaces in java.util.function cover the vast majority of common strategies one might want to apply, but it's also possible to define your own @FunctionalInterface instead. In cases where your desired interface coincides with an existing interface in java.util.function should you prefer defining a custom interface over reuse? Why or why not?

As a concrete example (though I'm looking for answers to the general question), should a function that expects a sorting operation take a Consumer<List<T>> and stipulate that it sort the input, or should a separate interface be defined that specifies a sort(List<T>) method?

Was it helpful?

Solution

First off this is largely a matter of style and API design, because callers can always convert from one equivalent functional interface to another via method references. So if you have a Consumer<T> instance and need to pass it to a method that expects a MyConsumer<T>, you can do so as:

myFunction(someConsumer::accept);

Since it's a matter of style reasonable people can disagree, but here's what I would suggest considering:

  • Your goal as the API author should be to minimize opportunities for misuse.
  • The interfaces defined in java.util.function are general-purpose, meaning their API contracts are very weak. They essentially stipulate nothing more than their method signatures imply, which can lead to errors if you actually have stronger requirements.
  • An interface you create can define a stricter contract, which can aid your users when they attempt to use your code. As Jules points out, simply defining a more precisely-named interface can significantly improve the readability and usability of your API.
  • That said, users become obligated to read your interface's contract in order to ensure they are following it correctly, which is an added burden and can also be a source of errors.

With those thoughts in mind, I would suggest defining your own interface any time your code requires more of its callers than the general-purpose interfaces imply.

For example a method that expects a sorting strategy would fail to behave correctly if passed a consumer that doesn't actually sort the inputs, meaning that accepting arbitrary Consumer<List<T>> implementations isn't desirable, and specifying your own Sorter interface makes your API clearer and less likely to accidentally be misused.

On the other hand many functional/strategy operations are fully compatible with the contracts of the general-purpose interfaces, in which case you shouldn't hesitate to reuse them. Even if some implementations aren't "correct" per se, as long as your code still behaves appropriately when passed such implementations there's no need to make the contract more complex by defining your own interface.

As another example consider Stream.map() - the intent of this function is to apply some operation to the input and return the transformed result, but there's nothing stopping a caller from passing in an operation that doesn't actually do that, such as one that just returns an arbitrary result, disregarding the input:

myIntStream.map(i -> random.nextInt())

This isn't in keeping with the spirit of map() (and it's an odd way to generate n random ints...), but it doesn't break anything - .map() still behaves correctly and as expected the stream now contains some number of random integers. Therefore it's reasonable that .map() takes an arbitrary Function and not some more strictly defined interface.

Licensed under: CC-BY-SA with attribution
scroll top