С появата на Java 8 бяха въведени много нови функции и подобрения. Някои от тях включват функционални интерфейси, ламбда, потоци, методи по подразбиране и препратки към методи.

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

Функционалното програмиране е парадигма, в която програмите се пишат чрез композиране на чисти функции. Тези функции могат да се третират като първокласни граждани на този език, което означава, че функциите могат да бъдат присвоявани на променливи, предавани като аргументи на други функции или връщани от други функции.

Тези функции връщат един и същ резултат за същия набор от входни аргументи и нямат странични ефекти, което означава, че състоянието на програмата или данните извън нейния обхват не се променят по време на нейното изпълнение. Освен това те не разчитат на външното състояние, което означава, че няма достъп или модифицира глобална или нелокална променлива. Следователно те се наричат ​​чисти функции.

Преминавайки към основната тема, функционалните интерфейси в Java са интерфейси, които съдържат само един абстрактен метод, известен също като функционален метод. Тези интерфейси могат също да съдържат методи по подразбиране и статични методи с имплементация заедно с единичен неимплементиран метод (функционален метод).

Функционалните интерфейси могат да бъдат анотирани с @FunctionalInterface за идентификация и компилатор, за да се гарантира, че интерфейсът не съдържа повече от един абстрактен метод.

От горната дефиниция е очевидно, че целта на функционалните интерфейси е да дефинират обекти, които се използват за извършване на едно конкретно действие.

Има два начина за използване на функционални интерфейси в Java. Единият е чрез използване на вградените интерфейси като част от пакета java.util.function, а вторият е чрез дефиниране на собствени персонализирани интерфейси.

Нека разгледаме пример за функционален интерфейс.

@FunctionalInterface
interface Multiplication {
  int multiply(int a, int b);
}

Можем да реализираме горния интерфейс по три начина:

  1. Класическа реализация на клас
class MultiplicationImpl implements Multiplication {
  @Override
  int multiply(int a, int b) {
    return a * b;
  }
}

class OurClass {
  public static void main(String... args) {

    Multiplication multiplication = new MultiplicationImpl();
    System.out.println(multiplication.multiply(2, 3));
  }
}

2. Анонимен клас

class OurClass {
  public static void main(String... args) {

    Multiplication multiplication = new Multiplication() {
      @Override
      int multiply(int a, int b) {
        return a * b;
      }
    };

    System.out.println(multiplication.multiply(2, 3));
  }
}

3. Ламбда изрази

class OurClass {
  public static void main(String... args) {

    Multiplication multiplication = (a, b) -> a * b;

    System.out.println(multiplication.multiply(2, 3));
  }
}

Тада🤩! Вижте колко по-чист и по-сбит е кодът с помощта на ламбда изрази.

Ламбда изразите се използват за деклариране на изпълнението на функционалния метод на функционален интерфейс. Те могат да се считат за анонимни методи, т.е. методи без име.

Функционалните интерфейси могат също да бъдат реализирани с помощта на препратки към методи, които са специален тип ламбда изрази. Обърнете внимание, че не е обхванато като част от тази история.

Сега нека разгледаме някои от вградените функционални интерфейси.

Функция‹T,R› :

Функционалният метод, т.е. apply(T)на този интерфейс, приема един аргумент като вход и връща резултат.

T — обозначава типа входен аргумент

R — обозначава вида на резултата

Function<Integer, Integer> func = (x) -> {
  return x * x;  
};
System.out.println(func.apply(3)); // Output: 9

Предикат‹T› :

Функционалният метод, т.е. test(T) на този интерфейс, приема един аргумент като вход и връща булев резултат.

T — обозначава типа входен аргумент

Predicate<Integer> isEven = x -> x % 2 == 0;
System.out.println(isEven.test(3)); // Output: false

Потребител‹T› :

Функционалният метод, т.е. accept(T) на този интерфейс, приема един аргумент като вход и не връща резултат. Може да се използва за дефиниране на поведение като отпечатване на стойност и добавяне на елементи към списък.

T — обозначава типа входен аргумент

Consumer<List<Integer>> printList = (list) -> {
    for(Integer num : list) {
        System.out.print(num + " ");
    }
};
printList.accept(List.of(1,2,3,4,5)); // Output: 1 2 3 4 5

Доставчик‹T› :

Функционалният метод, т.е. get() на този интерфейс, не приема входни данни и създава стойност. Няма изискване уникален резултат да се връща при всяко извикване. Представя се като доставчик на резултати.

T — обозначава вида на резултата

Supplier<Integer> randomNumber = () -> (int) (1 + (Math.random() * 10));

System.out.println(randomNumber.get()); // Outputs: <random number b/w 1 to 10>

Обобщение

Функционалните интерфейси в Java са интерфейси, които съдържат само един абстрактен метод. Те се използват за поддържане на функционален стил на програмиране и позволяват прилагането на абстрактния метод чрез ламбда израз.