W Java 8 istnieje pokaźna grupa interfejsów funkcjonalnych, które pokrótce będziemy omawiać. Warto zapoznać się ze wszystkimi i wiedzieć, że takie istnieją. Okazuje się, że odpowiadają one na niemalże wszystkie potrzeby programistów i tworzenie swoich interfejsów może być zbędne.

Zacznijmy więc, bo długa droga przed nami 😉 Dzisiaj pierwsza część serii artykułów opisująca interfejsy: BiConsumer, BiFunction, BinaryOperator

BiConsumer

Na początek zajmiemy się tym interfejsem, który posiada także metodę default:

@FunctionalInterface
interface BiConsumer<T, U> {
​
  //Przyjmuje dwa argument. Nie zwraca wartości
  void accept(T t, U u);
​
​
  // Wykonuje operację jedna po drugiej na przekazanych BiConsumer
  default java.util.function.BiConsumer<T, U> andThen(java.util.function.BiConsumer<? super T, ? super U> after) {
    Objects.requireNonNull(after);
​
    return (l, r) -> {
      accept(l, r);
      after.accept(l, r);
    };
  }
}

Metoda accpet nic nie zwraca, ale przyjmuje dwa argumenty, na których możemy wykonać operacje. Obiektem oczywiście może być cokolwiek, w naszym przypadku będzie to Double:

    BiConsumer<Double, Double> biConsumer = (x, y) -> {
      System.out.println("Dodawanie: " + (x + y));
    };
​
    biConsumerToString.accept(1.0, 2.0);

Wynik:

Dodawanie: 3

Metoda andThen służy do wykonania operacji na kilku obiektach typu BiConsumer. Może to wyglądać tak:

   BiConsumer<Double, Double> biConsumer = (x, y) -> {
      System.out.println("Dodawanie: " + (x + y));
    };
​
    BiConsumer<Double, Double> biConsumer1 = (x, y) -> {
      System.out.println("Mnożenie: " + (x * y));
    };
​
    BiConsumer<Double, Double> biConsumer2 = (x, y) -> {
      System.out.println("Dzielenie: " + (x / y));
    };
​
    biConsumer.andThen(biConsumer1).andThen(biConsumer2).accept(3.0, 2.0);

Wynik:

Dodawanie: 5
Mnożenie: 6
Dzielenie: 1

BiFunction

@FunctionalInterface
interface BiFunction<T, U, R> {
​
  //Przyjmuje dwa argument. Zwraca wynik.
  R apply(T t, U u);
​
​
  // Wykonuje BiFunction a potem przetwarza wynik w Function
  default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t, U u) -> after.apply(apply(t, u));
  }
}

Różnica z poprzednim interfejsem jest taka, że tym razem otrzymujemy zwrócony wynik:

    BiFunction<Double, Double, Double> biFunction = (x, y) -> {
      return x + y;
    };
    System.out.println(biFunction.apply(1.0, 1.0));

Wynik:

2.0

W metodzie andThen możemy przekazać obiekt typu Function, który dodatkowo przetwarza nam wynik naszej lambdy z interfejsu BiFunction. Liczby zostaną dodane za pomocą naszej lambdy, którą zdefiniowaliśmy wyżej, a potem jeszcze zostanie dodana do ich liczba PI.

Function<Double,Double> function = x-> x + Math.PI;
​System.out.println(biFunction.andThen(function).apply(1.0,1.0));

Wynik:

5.141592653589793

BinaryOperator

/**
 * Przyjmuje dwa argumenty tego samego typu i
 * zwraca wynik tego samego typu
 */
@FunctionalInterface
interface BinaryOperator<T> extends BiFunction<T, T, T> {


  //Zwraca mniejszy element zgodnie z przekazanym Comparatorem
  public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
    Objects.requireNonNull(comparator);
    return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
  }

  //Zwraca większy element zgodnie z przekazanym Comparatorem
  public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
    Objects.requireNonNull(comparator);
    return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
  }
}

BinaryOperator rozszerza interfejs BiFunction, który jest przedstawiony wyżej. Działanie tych interfejsów jest prawie takie same, z małą różnicą. BinaryOperator działa tylko na jednym i takim samym typie. BinaryOperator dodatkowo posiada dwie metody default minBy oraz maxBy.

Na początek metoda apply:

    BinaryOperator<Integer> binaryOperator = (x, y) -> x + y;
    System.out.println(binaryOperator.apply(4,5));

Wynik:

9

Jeżeli chcemy użyć metody minBy musimy zdefiniować Comparator. W tym przypadku użyjemy Comparator, który kryje się w klasie Integer.

 BinaryOperator<Integer> biByMin = BinaryOperator.minBy(Integer::compareTo);
 System.out.println(biByMin.apply(5, 7));

Wynik:

5

Podobna sytuacja z maxBy:

BinaryOperator<Integer> biByMax = BinaryOperator.maxBy(Integer::compareTo); System.out.println(biByMax.apply(123123, 897797789));

Wynik:

897797789