Во многих публикациях мы исследовали концепцию функционального программирования на разных языках, которые являются предметом обсуждения F # и Scala. Однако, поскольку я занимаюсь Java на своем рабочем месте, изучение той же концепции кажется интересным, и глаза открываются, потому что прошло много времени с тех пор, как я в последний раз серьезно использовал Java.

Функции высшего порядка

Как объясняется здесь Функции высшего порядка, что это такое? Функции высшего порядка - это простые функции, которые могут принимать функции в качестве аргументов и возвращать другую функцию в качестве результатов.

В современной Java это легко сделать. Синтаксис не самый лучший, и поскольку нет вывода типа, мы должны явно объявить тип функции, который в Java означает какой-то интерфейс . Посмотрим как.

Во-первых, предположим, что у нас есть набор объектов, то есть набор собак, и у нас есть функция, которая действует на каждую собаку. Мы хотим иметь возможность вызывать эту функцию для каждого объекта (собаки).

Давайте посмотрим, как мы могли бы создать такую ​​функцию.

@FunctionalInterface
interface DogAge {
    Integer apply(Dog dog);
}
List<Integer> getAges(List<Dog> dogs, DogAge f) {
    
    List<Integer> ages = new ArrayList<>();
    
    for (Dog dog : dogs) {
        ages.add(f.apply(dog));
    }
    
    return ages;
}

Мы определяем интерфейс, который, учитывая собаку, извлекает из нее некоторое целочисленное значение. Затем мы определяем функцию getAges, которая применяет переданную функцию (на данный момент interface) к каждой собаке.

Теперь нам нужно создать фактическую функцию, которую мы хотим применить к каждой собаке.

DogAge f = dog -> dog.getAge();

getAges(dogs, f);

Обратите внимание, что нам не нужно фактически определять реализацию DogAge, как это было сделано в более старой версии Java. Это будет следующий способ, но, пожалуйста, не используйте его больше.

DogAge dontUseMe = new DogAge() {
    @Override
    public Integer apply(Dog dog) {
        return dog.getAge();
    }
};

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

Мы можем пойти еще дальше и сделать следующее.

getAges(dogs, dog -> dog.getAge());

Здесь мы передаем функцию прямо в метод getAges.

Каким-то образом getAges является функцией более высокого порядка, поскольку она может принимать функции в качестве аргументов. Java сохраняет странную подпись, получая интерфейс, но я думаю, что это будет улучшено в будущих версиях языка.

Для сравнения, давайте определим getAges в Scala и посмотрим на различия. Кроме того, мы собираемся сразу изменить название функций, чтобы оно было более общим.

def extractStringFromDogs(dogs: List[Dog], f: Dog => String) = 
    dogs.map(f)

в Java мы могли бы это сделать.

@FunctionalInterface
interface DogMapper {
    String apply(Dog dog);
}
List<String> extractStringFromDogs(List<Dog> dogs, DogMapper f) {         
    return dogs.stream().map(dog -> f.apply(dog)).collect(Collectors.toList);
}

Бывает, что в Java уже есть структура, решающая ту же проблему. Это Функция ‹A, B›. Другими словами, мы могли бы это сделать.

List<String> extractStringFromDogs(List<Dog> dogs, Function<Dog, String> f) {
    return dogs.stream().map(dog -> f.apply(dog)).collect(Collectors.toList);
}
extractStringFromDogs(dogs, dog -> dog.getName());

А как насчет определения функций, которые фактически возвращают другие функции?

В Scala мы могли делать следующее.

scala> def sum(): (Int, Int) => Int = (a, b) => a + b
sum: ()(Int, Int) => Int
scala> sum()
res1: (Int, Int) => Int = $$Lambda$1067/2036949810@715f45c6
scala> sum()(4,5)
res2: Int = 9
scala> res1(2, 3)
res3: Int = 5

Здесь sum возвращает функцию, которую можно сохранить и оценить в другое время. Это очень мощная и важная конструкция функциональных языков. Можем ли мы сделать то же самое на Java?

Начнем с определения нашего собственного типа функции (Функциональный интерфейс) для этой конкретной проблемы.

@FunctionalInterface
interface TakeTwo {
    Integer apply(Integer a, Integer b);
}

Как мы могли видеть, TakeTwo семантически совпадает с тем, что мы определили в Scala.

Теперь мы можем снова определить метод sum.

TakeTwo sum() {
    return (a, b) -> a + b;
}
TakeTwo mySum = sum();

Integer finalSum = mySum.apply(5, 6);

Это в точности то же самое, что и в Scala, только в Scala синтаксис краткий и есть не нужно определять функциональный интерфейс для использования в качестве типа функции. Да, результат тот же.

Опять же, нам фактически не нужно определять TakeTwo самостоятельно, поскольку эквивалентный интерфейс уже определен в Java. называется BiFunction. Используя его, мы могли бы записать сумму следующим образом.

BiFunction<Integer, Integer, Integer> sum() {
    return (a, b) -> a + b;
}

Больше функциональных интерфейсов.

Чтобы поддержать усилия по функциональному программированию, Java включает в себя множество этих функциональных интерфейсов. Некоторые из них:

Потребитель

Java:

public interface Consumer<T> {
    void accept(T t);
    ....
}

Scala

T => Unit

Предикат

Java

public interface Predicate<T> {
    boolean test(T t);
    ...
}

Scala

T => boolean

Поставщик

Java

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

Scala

:=> T

Функция

Java

public interface Function<T, R> {
    R apply(T t);
    ...
}

Scala

T => R

BiFunction

Java

public interface BiFunction<T, U, R> {
     R apply(T t, U u);
     ...
}

Scala

(T, U) => R

Это лишь некоторые из типов функций (Функциональные интерфейсы), которые можно найти в новых Java и их аналог в Scala. Обратите внимание, что в Scala нам не нужно определять для них какие-либо интерфейсы, у нас просто есть функции, и мы можем определять их по своему усмотрению.

Выводы

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

С другой стороны, синтаксис Scala гораздо более точен и лучше показывает цель без необходимости создания интерфейсов как типов функций.

Я просто надеюсь, что Java продолжит развиваться, сокращая многословие и добавляя новые функциональные конструкции, потому что в конечном итоге мы, инженеры, получаем от них реальную пользу.