a Consumer which prints something on console. Then why shouldn't I directly use the System.out.println() method instead of creating a Consumer?
There's nothing wrong with printing something by simply using System.out.println()
. When it's a single almost isolated action like in your example, there's no room and no need to entail anything else. Conversely, if you would write in the main something like that it would look contrived:
Consumer<String> myConsumer = System.out::println;
myConsumer.accept("Hello Functions!");
There's no reason to replace a single statement that is clear to every beginner by the consumer here. Don't add the functionality that is not required, as the YAGNI principle suggests.
Let's look at the cases when the functional interfaces can be beneficial.
An Interface means a Contract
First of all, interfaces are meant to provide contract.
Thy are particularly useful when you want to generalize something, and functional interfaces are not an exception.
Let's consider the following example.
You have a couple of methods. One logs a message, another send some kind of request, the third sends an email, etc. And all these methods expects the same object, let's say of type Event
as parameter. That means, each of these methods could be used as an implementation of Supplier<Event>
.
Also, we have a group of methods that are calling these methods. And maybe they are not differ a lot. Let's assume each of the methods in this group is responsible for performing some kind of specific checks, then it processes the data and invokes one of the methods mentioned above.
With that, we can potentially refactor this second group of methods by contracting it to a single generalized method with such a signature:
public void processEvent(List<Event> events,
UnaryOperator<Event> operator,
Predicate<Event> predicate,
Consumer<Event> consumer)
Leave the Streams alone
There is a delusion that functions could be used only with streams. No, that totally fine if, let's say, your method that doesn't utilize stream anyhow expects a predicate (or other functional interface build in the JDK). That allow you to make the behavior of your classes more flexible. You can define a predicate inline when calling a method
doSomething(person, person -> person.getAge() > 30);
doSomething(person, person -> person.getAge() <= 25);
doSomethingElse(token, MyToken::isValid);
And inside the method, use it like that:
if (myPred.test(token)) { // do }
Another option is you to maintain a collection of functions in order to reuse the behavior they are representing and provide convenient access to them. For instance:
Map<MyEnum, Function<EventDTO, Event>> funcByEnum = ...;
Event myEvent = funcByEnum.get(enum).apply(dto);
Implementing Functional interfaces
Also, your classes can implement functional interfaces.
As an example from the JDK - a group of summary statistics classes like IntSummaryStatistics
that are implementing different flavors of consumer. And you can modify a summary statistics object by using method accept()
:
stat.accept(1000);
That allows to benefit from the self-explanatory names of methods defined by build in functional interfaces.
Names test
, accept
will be easily understood by anyone needs to read code (assuming they meat with a purpose of your methods).