Функциональные интерфейсы, часть 2. Best practices.



В части 1 был обзор функциональных интерфейсов (ФИ), а в этом посте — 4 рекомендации по их использованию.



1️⃣ Не реализуйте ФИ через анонимные классы.

Используйте лямбда-выражения и ссылки на методы. Это короче и понятнее.

Runnable r=() -> …

Runnable r=new Runnable{

@Override

public void run(){…}

}



2️⃣ Определите сценарии использования ФИ.

ФИ — это интерфейс, но с одним абстрактным методом. В него можно добавить сколько угодно методов с реализацией — дефолтных, приватных и статических. Один ФИ может расширять другой ФИ. Класс может реализовать ФИ через implements.



Чтобы код был простым и понятным, нужно разграничивать его компоненты:

🔸Цель обычного интерфейса - обозначить набор методов для будущих классов.

🔸Цель ФИ — передать набор инструкций через лямбды и ссылки на методы.



Один метод в ФИ - оптимальный вариант. Но есть случаи, когда дополнительные методы уместны, так как тесно связаны с самим интерфейсом.

Пример: интерфейс Predicate. Несмотря на то, что он функциональный, в нём есть 4 метода с заданной реализацией: and, or, isEqual и negate.



3️⃣ Используйте аннотацию FunctionalInterface.

Она не обязательна, но облегчает чтение кода.



4️⃣ Если используете ФИ как аргумент, отражайте тип ФИ в названии метода.



Пример: в классе 2 метода с одним именем и разными типами аргументов:

void adapt(Callable)

void adapt(Supplier)



Этот код компилируется, но у него 2 недостатка:

▪️Чтобы понять разницу между методами нужно читать код или документацию.

▪️В качестве аргумента нельзя передать лямбда-выражение, т.к оно подходит под оба типа:

 adapt(str->print(str))



Чтобы избежать этих проблем, используйте разные имена для методов. Хороший пример — класс CompletableFuture для многоэтапных вычислений. В его методах легко ориентироваться благодаря удачным названиям:

▫️thenAccept(Consumer)

▫️thenApply(Function)

▫️thenRun(Runnable)