В Java 8 появилось несколько новых функций, которые помогли сделать ее более универсальной и гибкой. Функция Java 8 делает возможным функциональное программирование. Вот некоторые из ключевых особенностей Java 8:
- Лямбда-выражения
- Функциональные интерфейсы
- API потоковой передачи
- Методы по умолчанию
- Ссылки на методы
- Необязательно
- 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.
Пожалуйста, подпишитесь, если вам понравилась статья.
Подпишитесь, чтобы быть в курсе следующих статей.