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

  1. Лямбда-выражения
  2. Функциональные интерфейсы
  3. API потоковой передачи
  4. Методы по умолчанию
  5. Ссылки на методы
  6. Необязательно
  7. API даты/времени

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

1. Лямбда-выражения

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

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

Синтаксис лямбда-выражения:

(parameters) -> { statements };

Символ -› отделяет параметры от тела лямбда-выражения.

Примеры:

(i) без аргументов

()-> System.out.println("lambda expression with no arguments");

(ii) один или несколько аргументов

// lambda expression with one argument
(a) -> System.out.println(String.format("Value of a is: %s", a);

// lambda expression with multiple arguments
(a, b) -> a + b;

// also argument type can be provided explicitely
(Integer a, Integer b) -> a + b;

(iii) более одного утверждения

(Integer a, Integer b) -> {  // parentheses needed for multiple statements
    System.out.println(String.format("Returning maximum from %s and %s", a, b));
    return (a > b) a : b;
};

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

// functional interface, we will see below in detail
interface MaxFinder {
    int findMax(int a, int b);
}

public class LambdaExpressionExample {
    public static void main(String[] args) {
        // return maximum of two numbers using lambda expression
        MaxFinder maxFinder = (a, b) -> (a > b) ? a : b;
        
        System.out.println(maxFinder.findMax(5, 10));
        System.out.println(maxFinder.findMax(15, 5));
    }
}

Вывод вышеуказанной программы:

Теперь давайте посмотрим, как можно использовать лямбда-выражение с циклом forEach:

public class LambdaExpressionExample {
  public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(4);
        nums.forEach(num -> System.out.println(String.format("Square of %s is %s", num, num * num)));
    }
}

Вывод вышеуказанной программы:

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

Интерфейс, который имеет только один абстрактный метод, называется функциональным интерфейсом, а также называется интерфейсами с одним абстрактным методом (интерфейсами SAM).

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

Java 8 предоставляет ряд встроенных функциональных интерфейсов, некоторые из них — Предикат, Функция, Потребитель и Поставщик.

Вы можете сослаться на приведенный выше простой пример, где мы определили функциональный интерфейс MaxFinder.

@FunctionalInterfaceаннотация используется для представления функционального интерфейса.

(i) Предикат

Интерфейс Predicate имеет единственный абстрактный метод test(T t), который принимает один аргумент и возвращает логическое значение.

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

T: тип входного аргумента метода.

Вот пример использования Predicate:

Predicate<Integer> isEligibleForVoting = (age) -> age >= 18;
boolean eligible = isEligibleForVoting.test(22); // true

(ii) Функция

Интерфейс Function имеет единственный абстрактный метод apply(T t), который принимает один аргумент и возвращает объект.

Обычно он используется для преобразования или отображения значений из одного типа в другой.

Вот пример использования Function:

Function<Integer, Integer> findSquare = (num) -> (num*num);
int square = findSquare.apply(5); // 25

(iii) Потребитель

Потребительский интерфейс имеет единственный абстрактный метод accept(T t), который принимает один аргумент и не возвращает результата.

Обычно он используется для выполнения побочных эффектов, таких как печать или регистрация.

Вот пример использования Consumer:

Consumer<String> printValue = (x) -> System.out.println(x);
printValue.accept("Java 8 features!"); // prints "Java 8 features!"

(iv) Поставщик

Интерфейс поставщика имеет единственный абстрактный метод get(), который не принимает аргументов и возвращает результат.

Обычно он используется для ленивой инициализации или для генерации случайных значений.

Вот пример использования Supplier:

Supplier<Integer> getRandomInteger = () -> (int) (Math.random() * 500);
int randomInteger = getRandomInteger.get(); // random integer between 0 and 500

Теперь давайте посмотрим на пример ниже, используя выше всех 4 функциональных интерфейса:

public class FunctionalInterfacesExample {

    public static void main(String[] args) {
        // Using Predicate functional interface
        Predicate<Integer> isEligibleForVoting = (age) -> age >= 18;
        boolean eligible = isEligibleForVoting.test(22); // true
        System.out.println(String.format("Eligible for voting: %s", eligible));
        
        // Using Function functional interface
        Function<Integer, Integer> findSquare = (num) -> (num * num);
        int square = findSquare.apply(5); // 25
        System.out.println(String.format("Square of 5: %s", square));
        
        // Using Consumer functional interface
        Consumer<String> printValue = (val) -> System.out.println(val);
        printValue.accept("Java 8 features!"); // prints "Java 8 features!"
        
        // Using Supplier functional interface
        Supplier<Integer> getRandomInteger = () -> (int) (Math.random() * 500);
        int randomInteger = getRandomInteger.get(); // random integer between 0 and 500
        System.out.println(String.format("Random number: %s", randomInteger));
    }

}

Вывод вышеуказанной программы:

3. Потоковое API

Java 8 Stream API — это мощный инструмент для обработки коллекций данных.

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

Поток – это упорядоченная последовательность элементов, которая может обрабатываться последовательно или параллельно.

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

Поток коллекции можно создать с помощью метода stream().

Streams API — это новое дополнение к Java Collections Framework, которое предоставляет декларативный способ обработки коллекций объектов. Потоки позволяют выполнять различные операции, такие как фильтрация, сопоставление и сокращение над наборами объектов в более кратким и читабельным способом, чем традиционные циклы for.

Stream API состоит из трех типов операций:

(i) Промежуточные операции:

Промежуточные операции используются для преобразования или фильтрации элементов потока.

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

Наиболее часто используемые промежуточные операции:

  • filter():используется для фильтрации значений на основе условия
  • sorted():используется для сортировки элементов
  • distinct():используется для поиска отдельных элементов
  • map():используется для сопоставления значения одного типа с другим.

(ii) Терминальные операции:

Терминальные операции используются для получения конечного результата из потока.

Они вызываются после всех промежуточных операций, примененных к потоку.

Наиболее часто используемые терминальные операции:

  • collect():используется для возвратарезультата промежуточных операций.
  • reduce():используется для сведения элементов потока в один элемент.
  • forEach():используется для перебора каждого элемента в потоке.
  • count(): возвращает количество элементов в этом потоке.

(iii) Операции с коротким замыканием:

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

Промежуточные и терминальные операции потока Java 8 могут быть короткими.

Наиболее часто используемые терминальные операции:

  • limit(): используется для возврата потока, как только достигается заданное предельное значение.
  • anyMatch(): используется для проверки соответствия каких-либо элементов этого потока предоставленному предикату.
  • findFirst(): возвращает необязательный параметр, описывающий первый элемент потока, или пустой необязательный параметр, если поток пуст.
  • findAny(): возвращает необязательный параметр, описывающий какой-либо элемент потока, или пустой необязательный параметр, если поток пуст.

Теперь давайте посмотрим на пример с использованием потока:

public class StreamAPIExample {

    public static void main(String[] args) {
        List<Integer> nums = Arrays.asList(3, 1, 4, 2, 6, 8, 5, 7);
        System.out.print("Given numbers list: ");
        nums.forEach(num -> System.out.print(String.format("%s, ", num)));
        System.out.println();
        
        // sort list of numbers
        List<Integer> sortedNums = nums.stream().sorted().collect(Collectors.toList());
        System.out.print("Sorted numbers are: ");
        sortedNums.forEach(num -> System.out.print(String.format("%s, ", num)));
        System.out.println();
        
        // filter all numbers less than 6
        List<Integer> filteredNums = nums.stream()
                .filter(i -> i < 6)
                .collect(Collectors.toList());
        System.out.print("Numbers less then 5 are: ");
        filteredNums.forEach(num -> System.out.print(String.format("%s, ", num)));
        System.out.println();
        
        // odd numbers
        long oddNumsCount = nums.stream()
                .filter(i -> i % 2 == 1).count();
        System.out.println(String.format("Total odd numbers are: %s", oddNumsCount));
        
        // print first 2 even numbers found from list while iterating stream
        System.out.print("first 2 even numbers found from list while iterating stream: ");
        nums.stream()
                .filter(num -> num % 2 == 0)
                .limit(2)
                .forEach(num -> System.out.print(String.format("%s, ", num)));
        System.out.println();
        
        // find squares of numbers
        List<Integer> squares = nums.stream()
                .map(num -> (num * num))
                .collect(Collectors.toList());
        System.out.print("Squares of numbers are: ");
        squares.forEach(square -> System.out.print(String.format("%s, ", square)));
    }

}

Вывод вышеуказанной программы:

Спасибо за прочтение, надеюсь, эта история помогла вам в изучении возможностей Java 8.

Пожалуйста, подпишитесь, если вам понравилась статья.

Подпишитесь, чтобы быть в курсе следующих статей.