лямбда выражения. Лямбда выражения. Лямбда выражения. Методы и конструкторы
Скачать 42.42 Kb.
|
Лямбда выражения. Методы и конструкторы Одним из преимуществ лямбд в java является то, что их можно передавать в качестве параметров в методы. Рассмотрим пример: public class LambdaApp { public static void main(String[] args) { Expression func = (n)-> n%2==0; int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; System.out.println(sum(nums, func)); // 20 } private static int sum (int[] numbers, Expression func) { int result = 0; for(int i : numbers) { if (func.isEqual(i)) result += i; } return result; } } interface Expression{ boolean isEqual(int n); } Функциональный интерфейс Expression определяет метод isEqual(), который возвращает true, если в отношении числа n действует какое-нибудь равенство. В основном классе программы определяется метод sum(), который вычисляет сумму всех элементов массива, соответствующих некоторому условию. А само условие передается через параметр Expression func. Причем на момент написания метода sum мы можем абсолютно не знать, какое именно условие будет использоваться. Само же условие определяется в виде лямбда-выражения: Expression func = (n)-> n%2==0; То есть в данном случае все числа должны быть четными или остаток от их деления на 2 должен быть равен 0. Затем это лямбда-выражение передается в вызов метода sum. При этом можно не определять переменную интерфейса, а сразу передать в метод лямбда-выражение: int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int x = sum(nums, (n)-> n > 5); // сумма чисел, которые больше 5 System.out.println(x); // 30 Начиная с JDK 8 в Java можно в качестве параметра в метод передавать ссылку на другой метод. Ссылка на метод передается в виде имя_класса::имя_статического_метода (если метод статический) или объект_класса::имя_метода (если метод нестатический). Ссылки на конструкторы название_класса::new. Ссылки на метод как параметры методов. Начиная с JDK 8 в Java можно в качестве параметра в метод передавать ссылку на другой метод. В принципе данный способ аналогичен передаче в метод лямбда-выражения.Ссылка на метод передается в виде имя_класса::имя_статического_метода (если метод статический) или объект_класса::имя_метода (если метод нестатический). Рассмотрим на примере: interface Expression{// функциональный интерфейс boolean isEqual(int n); } class ExpressionHelper{// класс, в котором определены методы static boolean isEven(int n){ return n%2 == 0; } static boolean isPositive(int n){ return n > 0; } } public class LambdaApp { public static void main(String[] args) { int[] nums = { -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5}; System.out.println(sum(nums, ExpressionHelper::isEven)); Expression expr = ExpressionHelper::isPositive; System.out.println(sum(nums, expr)); } private static int sum (int[] numbers, Expression func) { int result = 0; for(int i : numbers) { if (func.isEqual(i)) { result += i; } return result; } } Здесь также определен функциональный интерфейс Expression, который имеет один метод. Кроме того, определен класс ExpressionHelper, который содержит два статических метода. В принципе их можно было определить и в основном классе программы, но я вынес их в отдельный класс.В основном классе программы LambdaApp определен метод sum(), который возвращает сумму элементов массива, соответствующих некоторому условию. Условие передается в виде объекта функционального интерфейса Expression. В методе main два раза вызываем метод sum, передавая в него один и тот же массив чисел, но разные условия. Первый вызов метода sum: System.out.println(sum(nums, ExpressionHelper::isEven)); На место второго параметра передается ExpressionHelper::isEven, то есть ссылка на статический метод isEven() класса ExpressionHelper. При этом методы, на которые идет ссылка, должны совпадать по параметрам и результату с методом функционального интерфейса. При втором вызове метода sum отдельно создается объект Expression, который затем передается в метод:Expression expr = ExpressionHelper::isPositive; System.out.println(sum(nums, expr)); Использование ссылок на методы в качестве параметров аналогично использованию лямбда-выражений. Если нам надо вызвать нестатические методы, то в ссылке вместо имени класса применяется имя объекта этого класса: interface Expression{ boolean isEqual(int n);} class ExpressionHelper{ boolean isEven(int n){ return n%2 == 0; } } public class LambdaApp { public static void main(String[] args) { int[] nums = { -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5}; ExpressionHelper exprHelper = new ExpressionHelper(); System.out.println(sum(nums, exprHelper::isEven)); // 0 } private static int sum (int[] numbers, Expression func) { int result = 0; for(int i : numbers) { if (func.isEqual(i)) { result += i; } return result; } } Ссылки на конструкторы. Подобным образом мы можем использовать конструкторы: Например: public class LambdaApp { public static void main(String[] args) { UserBuilder userBuilder = User::new; User user = userBuilder.create("Tom"); System.out.println(user.getName()); } } interface UserBuilder{ User create(String name); } class User{ private String name; String getName(){ return name; } User(String n){ this.name=n; } } При использовании конструкторов методы функциональных интерфейсов должны принимать тот же список параметров, что и конструкторы класса, и должны возвращать объект данного класса. Более подробно про методы и конструкторы можно прочитать в статье “Лямбды как параметры и результаты методов”. При использовании конструкторов методы функциональных интерфейсов должны принимать тот же список параметров, что и конструкторы класса, и должны возвращать объект данного класса. Лямбды как результат методов Также метод в Java может возвращать лямбда-выражение. Рассмотрим следующий пример: interface Operation{ int execute(int x, int y); } public class LambdaApp { public static void main(String[] args) { Operation func = action(1); int a = func.execute(6, 5); System.out.println(a); // 11 int b = action(2).execute(8, 2); System.out.println(b); // 6 } private static Operation action (int number){ switch(number){ case 1: return (x, y) -> x + y; case 2: return (x, y) -> x - y; case 3: return (x, y) -> x * y; default: return (x,y) -> 0; } } } В данном случае определен функциональный интерфейс Operation, в котором метод execute принимает два значения типа int и возвращает значение типа int. Метод action принимает в качестве параметра число и в зависимости от его значения возвращает то или иное лямбда-выражение. Оно может представлять либо сложение, либо вычитание, либо умножение, либо просто возвращает 0. Стоит учитывать, что формально возвращаемым типом метода action является интерфейс Operation, а возвращаемое лямбда-выражение должно соответствовать этому интерфейсу. В методе main мы можем вызвать этот метод action. Например, сначала получить его результат - лямбда-выражение, которое присваивается переменной Operation. А затем через метод execute выполнить это лямбда-выражение: Функциональные интерфейсы стандартной библиотеки Java Java представила поддержку функционального программирования в выпуске Java версии 8. Этот конкретный выпуск также представил несколько новых концепций, в частности лямбда-выражения, ссылки на методы и множество функциональных интерфейсов. При обсуждении последних есть несколько функциональных интерфейсов, а именно — Потребитель (Consumer), Поставщик (Supplier), Предикат (Predicat) и Функция (Function), которые являются наиболее важными. Consumer (потребитель) Consumer — это функциональный интерфейс, который принимает один параметр на вход и не возвращает никаких выходных данных. На языке непрофессионала, как следует из названия, реализация этого интерфейса потребляет вводимые данные. Пользовательский интерфейс имеет два метода. void accept(T t); default Consumer Метод accept является единым абстрактным методом (SAM), который принимает один аргумент типа T. Тогда как другой метод andThen является методом по умолчанию и используется для композиции. Ниже приведен пример интерфейса consumer. Мы создали потребительскую реализацию, которая использует строку, а затем просто выводит ее на экран. Метод forEach принимает реализацию потребительского интерфейса. public void whenNamesPresentConsumeAll() { Consumer Stream cities.forEach(printConsumer); } Интерфейс Consumer имеет специфические типы реализаций для типов integer, double и long -> IntConsumer, DoubleConsumer и LongConsumer, как показано ниже: IntConsumer void accept(int x); DoubleConsumer void accept(double x); LongConsumer void accept(long x); Supplier (поставщик) Supplier — это простой интерфейс, указывающий, что данная реализация является поставщиком какого то результа. Этот интерфейс, однако, не накладывает никаких дополнительных ограничений, которые реализация поставщика должна возвращать при каждом новом получении результата. У поставщика есть только один метод get() и нет никаких других методов по умолчанию или статических методов. public void supplier() { Supplier DoubleSupplier doubleSupplier2 = Math::random; System.out.println(doubleSupplier1.get()); System.out.println(doubleSupplier2.getAsDouble()); } Одно из основных применений этого интерфейса это использование для включения отложенного выполнения. Это означает отсрочку выполнения до тех пор, пока оно не понадобится. Например, в классе Optional есть метод orElseGet. Этот метод срабатывает, если у option нет данных. Это показано ниже: public void supplierWithOptional() { Supplier Optional System.out.println(optionalDouble.orElseGet(doubleSupplier)); } Predicate (предикат) Интерфейс Predicate представляет собой логическую функцию аргумента. Он в основном используется для фильтрации данных из потока (stream) Java. Метод фильтра потока принимает предикат для фильтрации данных и возврата нового потока, удовлетворяющего предикату. У предиката есть метод test(), который принимает аргумент и возвращает логическое значение. public void testPredicate() { List Predicate names.stream().filter(nameStartsWithS).forEach(System.out::println); } Predicate public interface Predicate boolean test(T t); } import java.util.function.Predicate; public class LambdaApp { public static void main(String[] args) { Predicate System.out.println(isPositive.test(5)); // true System.out.println(isPositive.test(-7)); // false } } Function (функция) Интерфейс Function — это более общий интерфейс, который принимает один аргумент и выдает результат. В нем применяется единый абстрактный метод (SAM), который принимает аргумент типа T и выдает результат типа R. Одним из распространенных вариантов использования этого интерфейса является метод Stream.map. Пример использования показан ниже: public void testFunctions() { List Function List .map(nameMappingFunction).collect(Collectors.toList()); System.out.println(nameLength); } Функциональный интерфейс Function public interface Function R apply(T t); } import java.util.function.Function; public class LambdaApp { public static void main(String[] args) { Function System.out.println(convert.apply(5)); // 5 долларов } } BinaryOperator BinaryOperator public interface BinaryOperator<T> { T apply(T t1, T t2); } import java.util.function.BinaryOperator; public class LambdaApp { public static void main(String[] args) { BinaryOperator System.out.println(multiply.apply(3, 5)); // 15 System.out.println(multiply.apply(10, -2)); // -20 } } UnaryOperator UnaryOperator public interface UnaryOperator<T> { T apply(T t); } import java.util.function.UnaryOperator; public class LambdaApp { public static void main(String[] args) { UnaryOperator System.out.println(square.apply(5)); // 25 } } Более подробно об интерфейсах можно прочитать в статьях “Функциональные интерфейсы в Java 8 → Consumer, Supplier, Predicate и Function. Что к чему и зачем нужны” и “Встроенные функциональные интерфейсы”. В JDK 8 вместе с самой функциональностью лямбда-выражений также было добавлено некоторое количество встроенных функциональных интерфейсов, которые мы можем использовать в различных ситуациях и в различные API в рамках JDK 8. В частности, ряд далее рассматриваемых интерфейсов широко применяется в Stream API - новом прикладном интерфейсе для работы с данными. Рассмотрим основные из этих интерфейсов: Predicate Consumer Function Supplier UnaryOperator BinaryOperator BinaryOperator UnaryOperator Function 1 2 3 Consumer Consumer 1 2 3 public interface Consumer void accept(T t); } Например: 1 2 3 4 5 6 7 8 9 10 import java.util.function.Consumer; public class LambdaApp { public static void main(String[] args) { Consumer printer.accept(600); // 600 долларов } } Supplier Supplier 1 2 3 public interface Supplier T get(); } Например: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import java.util.Scanner; import java.util.function.Supplier; public class LambdaApp { public static void main(String[] args) { Supplier Scanner in = new Scanner(System.in); System.out.println("Введите имя: "); String name = in.nextLine(); return new User(name); }; User user1 = userFactory.get(); User user2 = userFactory.get(); System.out.println("Имя user1: " + user1.getName()); System.out.println("Имя user2: " + user2.getName()); } } class User{ private String name; String getName(){ return name; } User(String n){ this.name=n; } } Консольный вывод: |