Введение в Lambda
Выражения Lambda являются важной новой функцией в Java SE 8. Выражение Lambda похоже на метод, который предоставляет нормальный список параметров и тело (тело, которое может быть выражением или кодовым блоком), который использует эти параметры.
Выражения Lambda также улучшают библиотеку сбора. Java SE 8 добавляет 2 пакета, которые управляют пакетными операциями в данных сбора: java.util.function пакет и пакет java.util.stream . Поток похож на итератор, но со многими дополнительными функциями. В целом, выражения и потоки Lambda являются самыми большими изменениями, поскольку язык Java добавляет дженерики и аннотации.
Выражения Lambda являются по сути анонимными методами, и их основной слой реализуется посредством invokedynamic директив для создания анонимных классов. Он обеспечивает более простой синтаксис и метод записи, позволяя заменить функциональные интерфейсы на выражения. В глазах некоторых людей Lambda может сделать ваш код более кратким и не использовать его вообще - эта точка зрения, безусловно, в порядке, но важно то, что Lambda закрывает Java. Благодаря поддержке Lamdba для коллекций Lambda значительно улучшила производительность при прохождении коллекций в условиях многоядерных процессоров. Кроме того, мы можем обрабатывать коллекции в виде потоков данных, что очень привлекательно.
Lambda Syntax
Синтаксис Lambda чрезвычайно прост, похожий на следующую структуру:
(параметры) -> выражение
или
(параметры) -> {операторы; }Выражения Lambda состоят из трех частей:
1. Параматы: список формальных параметров В аналогичных методах параметры здесь являются параметрами в функциональном интерфейсе. Типы параметров здесь могут быть явно объявлены или не объявлены, но неявно выводятся JVM. Кроме того, когда есть только один тип вывода, скобки могут быть опущены.
2. ->: это можно понимать как «использование»
3. Тело метода: это может быть выражение или кодовый блок, это реализация метода в функциональном интерфейсе. Кодовый блок может вернуть значение или ничего не изменить. Кодовый блок здесь эквивалентен методу метода метода. Если это выражение, вы также можете вернуть значение или ничего не вернуть.
Давайте используем следующие примеры, чтобы проиллюстрировать:
//Example 1: No need to accept parameters, directly return 10()->10//Example 2: Accept two parameters of int type and return the sum of these two parameters (int x,int y)->x+y;//Example 2: Accept two parameters of x and y, the type of this parameter is inferred by the JVM based on the context, and returns the sum of the two parameters (x,y)->x+y;//Example 3: Примите строку и распечатайте строку для управления, без реверса, Result (string name)-> system.out.println (name); // Пример 4: Примите имя параметра подключенного типа и распечатайте строку для консоли name-> system.out.println (имя); // Пример 5: Принять два строки parameters и вывод их Пол)-> {System.out.println (name); System.out.println (sex)} // Пример 6: принять параметр x и вернуть дважды параметр x-> 2*xГде использовать лямбду
В [функциональном интерфейсе] [1] мы знаем, что целевой тип выражения Lambda является функциональным интерфейсом - каждый лямбда может соответствовать данному типу через конкретный функциональный интерфейс. Следовательно, выражение лямбды может быть применено в любом месте, которое соответствует его целевому типу. Выражение Lambda должно иметь тот же тип параметра, что и описание функции абстрактной функции функционального интерфейса, его тип возврата также должен быть совместим с возвращающимся типом абстрактной функции, а исключения, которые он может добавить, ограничены диапазоном описания функции.
Далее, давайте посмотрим на пример пользовательского функционального интерфейса:
@FunctionalInterface Interface Converter <F, T> {T Convert (f из);}Во -первых, используйте интерфейс традиционным способом:
Преобразователь <string, integer> converter = new Converter <String, Integer> () {@Override public integer convert (string from) {return integer.valueof (from); }}; Integer result = converter.convert ("200"); System.out.println (результат);Очевидно, что нет проблем с этим, поэтому следующее - это момент, когда Lambda выходит на поле, используя Lambda для реализации интерфейса преобразователя:
Преобразователь <string, integer> converter = (param) -> integer.valueof (param); Integer result = converter.convert ("101"); System.out.println (результат);Через приведенный выше пример я думаю, что у вас есть простое понимание использования Lambda. Ниже мы используем обычно используемые для демонстрации:
В прошлом мы могли писать этот код:
новый поток (new Runnable () {@Override public void run () {System.out.println ("hello lambda");}}). start ();В некоторых случаях большое количество анонимных классов может сделать код выглядеть загроможденным. Теперь вы можете использовать лямбду, чтобы сделать это проще:
новый поток (() -> system.out.println ("hello lambda")). start ();Ссылка на метод
Ссылка на метод - это упрощенный способ написать Lambda Expressions. Способный метод на самом деле является реализацией тела метода выражения Lambda, а его синтаксическая структура:
Objectref :: methodname
Левая сторона может быть именем класса или имя экземпляра, середина - это ссылочный символ метода »::", а правая сторона - соответствующее имя метода.
Ссылки на метод разделены на три категории:
1. Справочник по статическому методу
В некоторых случаях мы могли бы написать код так:
Общедоступный класс SERECTENCETEST {public static void main (string [] args) {converter <string, integer> converter = new Converter <String, Integer> () {@Override public integer convert (string from) {return referenceTest.string2int (from); }}; Converter.convert ("120"); } @FunctionalInterface Interface Converter <f, t> {t Convert (f из); } static int string2int (string from) {return integer.valueof (from); }}В настоящее время, если вы используете статические ссылки, код будет более кратким:
Преобразователь <string, integer> converter = serireceTest :: string2int; Converter.convert ("120");2. Ссылка на метод экземпляра
Мы могли бы также написать код так:
Общедоступный класс SERECTENCETEST {public static void main (string [] args) {converter <string, integer> converter = new Converter <String, Integer> () {@Override public integer convert (string from) {return new helper (). string2int (from); }}; Converter.convert ("120"); } @FunctionalInterface Interface Converter <f, t> {t Convert (f из); } static class helper {public int string2int (string from) {return integer.valueof (of); }}}Кроме того, использование примеров методов для ссылки будет показаться более кратким:
Helper Helper = new Helper (); Преобразователь <string, integer> converter = helper :: string2int; Converter.convert ("120");3. Ссылка на метод конструктора
Теперь давайте продемонстрируем ссылки на конструкторы. Сначала мы определяем животное родительского класса:
класс животное {частное название строки; частный int возраст; Общественное животное (название строки, int age) {this.name = name; this.age = возраст; } public void behave () {}} Далее мы определяем два подкласса животного: Dog、Bird
Public Class Bird расширяет Animal {public Bird (String name, int age) {super (имя, возраст); } @Override public void behave () {System.out.println ("fly"); }} класс Dog расширяет Animal {public Dog (String name, int age) {super (имя, возраст); } @Override public void behave () {System.out.println ("run"); }}Затем мы определяем заводской интерфейс:
Интерфейс фабрика <t Extens Animal> {t Create (String name, int age); }Далее мы будем использовать традиционный метод для создания объектов классов собак и птиц:
Factory Factory = new Factory () {@Override public Animal Create (String name, int age) {вернуть новую собаку (имя, возраст); }}; factory.create ("псевдоним", 3); factory = new Factory () {@Override public Animal Create (String name, int age) {вернуть новую птицу (имя, возраст); }}; factory.create ("smook", 2);Я написал более десяти кодов, чтобы создать два объекта. Теперь давайте попробуем использовать ссылку на конструктор:
Фабрика <Animal> Dogfactory = Dog :: new; Animal Dog = Dogfactory.create («псевдоним», 4); Фабрика <Bird> BirdFactory = Bird :: new; Bird Bird = BirdFactory.Create ("Smook", 3); Таким образом, код выглядит чистым и аккуратным. При использовании Dog::new для проникновения объектов, выберите соответствующую функцию создания, подписав функцию Factory.create .
Ограничения домена и доступа Lambda
Домен - это область применения, и параметры в списке параметров в выражении лямбда действительны в рамках выражения лямбда (домен). В выражении Lambda можно получить доступ к внешним переменным: локальные переменные, переменные класса и статические переменные, но степень ограничений работы отличается.
Доступ к локальным переменным
Локальные переменные вне выражения лямбды будут неявно скомпилированы JVM к конечному типу, поэтому к ним можно только доступ, но не модифицировано.
Общедоступный класс SERECTENCETEST {public static void main (string [] args) {int n = 3; Рассчитайте Calculate = Param -> {// n = 10; Скомпилируйте ошибку возврата n + param; }; Рассчитывать. Каликуляция (10); } @FunctionalInterface Culdulate {int canculate (int value); }}Доступ к статике и переменным членам
Внутри выражений Lambda, статические и члены переменные, можно прочитать и записаться.
Общедоступный класс Referencetest {public int count = 1; общественный статический int num = 2; public void test () {Рассчитайте Calculate = Param -> {num = 10; // Модифицировать Статическую переменную COUNT = 3; // Модифицировать переменную члена возврат n + param; }; Рассчитывать. Каликуляция (10); } public static void main (string [] args) {} @functionalInterface Интерфейс Рассчитайте {int Canculate (int value); }}Lambda не может получить доступ к методу функции по умолчанию
Java8 улучшает интерфейсы, включая методы по умолчанию, которые могут добавлять определения ключевых слов по умолчанию в интерфейсы. Здесь нужно отметить, что доступ к методам по умолчанию не поддерживает внутренне.
Практика Lambda
В разделе [Функциональный интерфейс] [2] мы упомянули, что многие функциональные интерфейсы встроены в пакет java.util.function , и теперь мы объясним обычно используемые функциональные интерфейсы.
Предикат интерфейс
Введите параметр и верните Boolean значение, которое содержит много методов по умолчанию для логического суждения:
@Test public void predicttest () {Predicate <string> прогноз = (s) -> s.length ()> 0; логический тест = прогноз.test ("test"); System.out.println ("Длина строки больше 0:" + test); тест = прогноз.test (""); System.out.println ("Длина строки больше 0:" + test); Предикат <object> pre = Objects :: nonnull; Объект ob = null; тест = pre.test (ob); System.out.println ("Объект не пуст:" + test); OB = новый объект (); тест = pre.test (ob); System.out.println ("Объект не пуст:" + test); }Функциональный интерфейс
Получить параметр и вернуть один результат. Метод по умолчанию ( andThen ) может объединять несколько функций вместе, чтобы сформировать результат составной Funtion (с входом, выход).
@Test public void functionSt () {function <string, integer> tointeger = integer :: valueof; // Результат выполнения TOINTEGER используется в качестве входного сигнала во вторую функцию бэктострорирования <String, String> BacktoString = tointeger.andThen (String :: valueOf); String result = backtoString.apply ("1234"); System.out.println (результат); Function <Integer, Integer> add = (i) -> {System.out.println ("Вход Frist:" + i); вернуть i * 2; }; Function <Integer, Integer> Zero = add.andThen ((i) -> {System.out.println ("Второй ввод:" + i); return i * 0;}); Integer res = Zero.Apply (8); System.out.println (res); }Интерфейс поставщика
Возвращает результат данного типа. В отличие от Function , Supplier не нужно принимать параметры (поставщик, с выводом, но без ввода)
@Test public void suppliertest () {поставщик <string> поставщик = () -> "Значение специального типа"; String s = поставщик.get (); System.out.println (s); }Потребительский интерфейс
Представляет операции, которые необходимо выполнить на одном входном параметре. В отличие от Function , Consumer не возвращает значение (потребитель, вход, без вывода)
@Test public void consumerStest () {Consumer <Integer> add5 = (p) -> {System.out.println ("Old Value:" + P); P = P + 5; System.out.println ("Новое значение:" + P); }; add5.accept (10); } Использование вышеупомянутых четырех интерфейсов представляет четыре типа в пакете java.util.function . После понимания этих четырех функциональных интерфейсов другие интерфейсы будут легко понять. Теперь давайте сделаем простое резюме:
Predicate используется для логического суждения, Function используется в местах, где есть входы и выходы, Supplier используется в местах, где нет ввода и выходов, а Consumer используется в местах, где есть вход и нет выходов. Вы можете знать сценарии использования, основанные на значении его имени.
Транслировать
Lambda делает закрытие для Java 8, что особенно важно в операциях сбора: Java 8 поддерживает функциональные операции в потоке объектов сбора. Кроме того, потоковой API также интегрирован в API сбора, что позволяет пакетным операциям на объектах сбора.
Давайте познакомимся с потоком.
Поток представляет поток данных. Он не имеет структуры данных и сами не хранит элементы. Его операции не изменят источник, а генерируют новый поток. В качестве интерфейса для рабочих данных он обеспечивает фильтрацию, сортировку, картирование и регулирование. Эти методы разделены на две категории в соответствии с типом возврата: любой метод, который возвращает тип потока, называется промежуточным методом (промежуточная операция), а остальные являются методами завершения (полная операция). Метод завершения возвращает значение какого -то типа, в то время как промежуточный метод возвращает новый поток. Вызовов промежуточных методов обычно прикован, и процесс будет образовывать трубопровод. Когда будет вызван окончательный метод, он приведет к немедленному потреблению значения из трубопровода. Здесь мы должны помнить: операции по потоку работают как можно более «отсроченными», что мы часто называем «ленивыми операциями», что поможет сократить использование ресурсов и повысить производительность. Для всех промежуточных операций (кроме отсортированных) они запускаются в режиме задержки.
Stream не только предоставляет мощные возможности работы данных, но и, что более важно, Stream поддерживает как последовательный, так и параллелизм. Параллелизм позволяет потоку иметь лучшую производительность на многоядерных процессорах.
Процесс использования потока имеет фиксированный шаблон:
1. Создайте поток
2. Через промежуточные операции «Измените» исходный поток и генерируйте новый поток
3. Используйте операцию завершения, чтобы получить конечный результат
То есть
Create -> change -> завершить
Создание потока
Для коллекции его можно создать, позвонив в Collection's stream() или parallelStream() . Кроме того, эти два метода также реализованы в интерфейсе сбора. Для массивов они могут быть созданы с помощью статического метода потока of(T … values) . Кроме того, массивы также обеспечивают поддержку потоков.
В дополнение к созданию потоков на основе коллекций или массивов выше, вы также можете создать пустой поток через Steam.empty() или использовать потоковое generate() для создания бесконечных потоков.
Давайте возьмем последовательный поток в качестве примера, чтобы проиллюстрировать несколько часто используемых методов промежуточного и завершения потока. Сначала создайте коллекцию списка:
List <string> lists = new ArrayList <string> (); lists.add ("a1"); lists.add ("a2"); lists.add ("b1"); lists.add ("b2"); lists.add ("b3"); lists.add ("O1");Промежуточный метод
Фильтр
В сочетании с предикатом интерфейс фильтровал все элементы в потоковом объекте. Эта операция является промежуточной операцией, что означает, что вы можете выполнять другие операции на основе результата, возвращаемого операцией.
public static void StreamfilterTest () {lists.stream (). Filter ((s -> s.startswith ("a"))). foreach (system.out :: println); // эквивалентно вышеуказанному предикату операции <string> predicate = (s) -> s.startswith ("a"); lists.stream (). Filter (Predicate) .foreach (System.out :: println); // Предикат непрерывного фильтрации <string> predicate1 = (s -> s.endswith ("1")); lists.stream (). Filter (Predicate) .filter (Predicate1) .foreach (System.out :: println); }Сортируется (отсортировано)
В сочетании с интерфейсом компаратора эта операция возвращает представление о сортированном потоке, и порядок исходного потока не изменится. Правила сбора указаны через компаратор, а по умолчанию сортируют их в естественном порядке.
public static void StreamSortEdTest () {System.out.println ("Comporator" по умолчанию "); Lists.Stream (). Sorted (). Filter ((S -> S.StartSwith ("a"))). Foreach (System.out :: println); System.out.println ("Пользовательский компаратор"); Lists.Stream (). Sorted ((P1, P2) -> p2.compareto (p1)). Filter ((s -> s.startswith ("a"))). foreach (system.out :: println); }Карта (карта)
В сочетании с интерфейсом Function эта операция может отобразить каждый элемент в объекте потока в другой элемент, реализуя преобразование типа элемента.
public static void Streammaptest () {lists.stream (). Map (string :: touppercase) .sorted ((a, b) -> b.compareto (a)). foreach (System.out :: println); System.out.println («Пользовательские правила картирования»); Function <string, string> function = (p) -> {return p + ".txt"; }; lists.stream (). map (string :: touppercase) .map (function) .sorted ((a, b) -> b.compareto (a)). foreach (system.out :: println); }Вышеуказанное кратко представляет три обычно используемые операции, которые значительно упрощают обработку коллекции. Далее мы представляем несколько способов завершить:
Метод отделки
После процесса «преобразования» необходимо получить результат, то есть операция завершена. Давайте посмотрим на соответствующие операции ниже:
Соответствовать
Используется для определения того, соответствует ли predicate объект потока и, наконец, возвращает результат Boolean типа, например:
public static void StreamtchTest () {// return true, если один элемент в объекте потока соответствует Boolean AnyStartWitha = lists.Stream (). AnyMatch ((s -> s.startSwith ("a")); System.out.println (AnyStartWitha); // возвращать true, когда каждый элемент в объекте потока соответствует Boolean allStartWitha = lists.Stream (). AllMatch ((s -> s.startSwith ("a"))); System.out.println (AllStartWitha); }Собирать
После преобразования мы собираем элементы преобразованного потока, такие как сохранение этих элементов в коллекцию. В настоящее время мы можем использовать метод сбора, предоставленный потоком, например:
public static void Streamcollecttest () {list <string> list = lists.stream (). filter ((p) -> p.startswith ("a")). sorted (). collect (collectors.tolist ()); System.out.println (список); }Считать
SQL-подобное количество используется для подсчета общего количества элементов в потоке, например:
public static void StreamCountTest () {long count = lists.stream (). filter ((s -> s.startswith ("a"))). count (); System.out.println (count); }Уменьшать
Метод reduce позволяет нам рассчитывать элементы по -своему или ассоциировать элементы в потоке с некоторой шаблоном, например:
public static void StreamReducetest () {необязательный <string> необязательный = lists.stream (). sorted (). creat ((s1, s2) -> {system.out.println (s1 + "|" + s2); return s1 + "|" + s2;}); }Результаты выполнения следующие:
A1 | A2A1 | A2 | B1A1 | A2 | B1 | B2A1 | A2 | B1 | B2 | B3A1 | A2 | B1 | B2 | B3 | O1
Параллельный поток против серийного потока
До сих пор мы ввели широко используемые промежуточные и завершенные операции. Конечно, все примеры основаны на последовательном потоке. Далее мы представим ключевую драму - параллельный поток (параллельный поток). Параллельный поток реализован на основе структуры параллельного разложения вилки-младшего и делит, набор больших данных на несколько небольших данных и передает его различным потокам для обработки. Таким образом, производительность будет значительно улучшена при ситуации многоядерной обработки. Это согласуется с концепцией дизайна MapReduce: большие задачи становятся меньше, а небольшие задачи переназначены на разные машины для выполнения. Но небольшая задача здесь передается разным процессорам.
Создайте параллельный поток через parallelStream() . Чтобы проверить, могут ли параллельные потоки действительно улучшить производительность, мы выполняем следующий тестовый код:
Сначала создайте большую коллекцию:
List <string> biglists = new ArrayList <> (); для (int i = 0; i <10000000; i ++) {uuid uuid = uuid.randomuuid (); biglists.add (uuid.toString ()); }Проверьте время для сортировки под серийными потоками:
private static void otparallelstreamsortedtest (list <string> biglists) {long starttime = system.nanotime (); long count = biglists.stream (). sorted (). count (); Long EndTime = System.Nanotime (); Long Millis = TimeUnit.nanoseconds.tomillis (EndTime - StartTime); System.out.println (System.out.printf ("Serial Sort: %D MS", Millis)); }Проверьте время для сортировки в параллельных потоках:
Частный статический void parallelstreamsortedtest (list <string> Biglists) {long startTime = System.nanotime (); long count = biglists.parallelStream (). Sorted (). Count (); Long EndTime = System.Nanotime (); Long Millis = TimeUnit.nanoseconds.tomillis (EndTime - StartTime); System.out.println (System.out.printf («Parallelsorting: %D MS», Millis)); }Результаты следующие:
Серийный сортировка: 13336 мс
Параллельный вид: 6755 мс
Увидев это, мы обнаружили, что производительность улучшилась примерно на 50%. Вы также думаете, что сможете использовать parallel Stream в будущем? На самом деле, это не так. Если вы все еще один ядерный процессор сейчас, и объем данных не большой, серийная потоковая передача все еще является таким хорошим выбором. Вы также обнаружите, что в некоторых случаях производительность серийных потоков лучше. Что касается конкретного использования, вам нужно сначала проверить его, а затем решить в соответствии с фактическим сценарием.
Ленивая операция
Выше мы говорили о потоке, работающем как можно позже, и здесь мы объясняем это, создав бесконечный поток:
Сначала используйте метод generate потока для создания естественной численной последовательности, а затем преобразовать поток через map :
// класс последовательности Incremental Natureseq реализует поставщика <long> {long value = 0; @Override public long get () {value ++; возвращаемое значение; }} public void StreamCreateSt () {stream <long> stream = stream.generate (new Natureseq ()); System.out.println ("Количество элементов:"+stream.map ((param) -> {return param;}). Limit (1000) .count ()); }Результат исполнения:
Количество элементов: 1000
Мы обнаружили, что в начале любые промежуточные операции (такие как filter,map и т. Д., Но sorted не может быть сделано) в порядке. То есть процесс выполнения промежуточных операций в потоке и выживания нового потока не вступает в силу немедленно (или операция map в этом примере будет работать навсегда и быть заблокирован), и поток начинает рассчитывать, когда возникает метод завершения. С помощью метода limit() преобразовать этот бесконечный поток в конечный поток.
Суммировать
Выше всего содержимое быстрого введения в Java Lambda. Прочитав эту статью, у вас есть более глубокое понимание Java Lambda? Я надеюсь, что эта статья будет полезна всем, чтобы узнать Java Lambda.