Skip to main content

Функциональные интерфейсы*

Теоретический блок

Вам нужно просто прочитать его. Команды выполнять не требуется.

В java определён ряд готовых интерфейсов, которые часто используются при написании кода. Эти интерфейсы содержат всего один метод.

Predicate<T>

Функциональный интерфейс Predicate<T> проверяет соблюдение некоторого условия. Если оно соблюдается, то возвращается значение true. В скобках <> указывается тип данных, с которым будет работать данный класс. Классы, использующие такие скобки, называются шаблонами (Generics). Общая идея заключается в том, что если группа классов имеет одинаковое поведение для разных типов, то нет смысла писать отдельные классы для каждого из них. Проще сделать шаблон, в который java будет просто подставлять нужный нам тип. Но в шаблонах нельзя использовать базовые типы, вместо них нужно указывать их классы-оболочки.

public interface Predicate<T> {
boolean test(T t);
}
public static void main(String[] args) {
Predicate<Integer> checkInt = new Predicate<>() {
@Override
public boolean test(Integer value) {
return value > 0;
}
};
Predicate<Double> checkDouble = new Predicate<>() {
@Override
public boolean test(Double value) {
return value > 0.0 && value < 10.0;
}
};
}

Для вызова конструктора класса-шаблона не нужно указывать в скобках тип, достаточно указать его при объявлении переменной.

System.out.println(checkInt.test(5));
System.out.println(checkDouble.test(-1.0));

После этого мы можем в любом месте метода main вызывать проверку чисел при помощи метода test.

Но такая форма записи довольно громоздкая. Чтобы её упростить, используют lambda-выражения.

Общий вид lambda-выражения: (переменные)->{тело метода функционального интерфейса} Набор переменных определяется функциональным интерфейсом, создание которого мы хотим упростить.

public static void main(String[] args) {
Predicate<Integer> checkInt = (value) -> {
return value > 0;
};
Predicate<Double> checkDouble = (value) -> {
return value > 0.0 && value < 10.0;
};

System.out.println(checkInt.test(5));
System.out.println(checkDouble.test(-1.0));
}

Если lambda-выражение не содержит несколько команд, а просто возвращает результат, то можно упростить его ещё сильнее: (переменные)->результат.

public static void main(String[] args) {
Predicate<Integer> checkInt = (value) -> value > 0;
Predicate<Double> checkDouble = (value) -> value > 0.0 && value < 10.0;

System.out.println(checkInt.test(5));
System.out.println(checkDouble.test(-1.0));
}

BinaryOperator<T>

BinaryOperator<T> принимает в качестве параметра два объекта типа T, выполняет над ними бинарную операцию и возвращает ее результат также в виде объекта типа T:

public interface BinaryOperator<T> {
T apply(T t1, T t2);
}
public static void main(String[] args) {
BinaryOperator<Integer> multiply = (x, y) -> x*y;

System.out.println(multiply.apply(3, 5)); // 15
System.out.println(multiply.apply(10, -2)); // -20
}

UnaryOperator<T>

UnaryOperator<T> принимает в качестве параметра объект типа T, выполняет над ними операции и возвращает результат операций в виде объекта типа T:

public interface UnaryOperator<T> {
T apply(T t);
}
public static void main(String[] args) {
UnaryOperator<Integer> square = x -> x*x;
System.out.println(square.apply(5)); // 25
}

Function<T,R>

Функциональный интерфейс Function<T,R> представляет функцию перехода от объекта типа T к объекту типа R:

public interface Function<T, R> {
R apply(T t);
}
public static void main(String[] args) {
Function<Integer, String> convert = x-> String.valueOf(x) + " долларов";
System.out.println(convert.apply(5)); // 5 долларов
}

Consumer<T>

Consumer<T> выполняет некоторое действие над объектом типа T, при этом ничего не возвращая:

public interface Consumer<T> {
void accept(T t);
}
public static void main(String[] args) {
Consumer<Integer> printer = x-> System.out.printf("%d долларов \n", x);
printer.accept(600); // 600 долларов
}

Supplier<T>

Supplier<T> не принимает никаких аргументов, но должен возвращать объект типа T:

public interface Supplier<T> {
T get();
}

При помощи Supplier<T> часто реализовывают фабрики. Это специальный приём(паттерн проектирования), который в общем виде создаёт объекты разных классов одним методом. Но все эти классы должны быть потомками единого предка.

Например, создадим фабрику пользователей:

public static void main(String[] args) {
Supplier<User> userFactory = ()->{
Scanner in = new Scanner(System.in);
System.out.println("Введите имя: ");
String name = in.nextLine();
return new User(name);
};

User user1 = userFactory.get();
User user2 = userFactory.get();

System.out.println("Имя user1: " + user1.getName());
System.out.println("Имя user2: " + user2.getName());
}

Comparable <T>

Comparable<T> принимает два аргумента и возвращает -1, если первый объект меньше второго, 0, если они равны и 1, если больше. Он нужен для использования готовых методов сортировки в коллекциях, о них мы будем говорить позже

public interface Comparator<T> {
int compare(T a, T b);
}
public static void main(String[] args) {
Comparator<User> personComparator = (p1, p2) -> p1.getName().compareTo(p2.getName());
User person1 = new User("Tom");
User person2 = new User("Nick");
User person3 = new User("Alice");
User person4 = new User("Tom");
System.out.println(personComparator.compare(person1, person2)); // 6
System.out.println(personComparator.compare(person3, person2)); // -13
System.out.println(personComparator.compare(person1, person4)); // 0
}

При реализации компаратора мы использовали готовый метод сравнения строк.