Потоки Java 8

Java 8 新特性Новые возможности Java 8


Java 8 API добавил новую абстракцию под названием Stream, которая позволяет обрабатывать данные декларативным способом.

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

Stream API может значительно повысить производительность Java-программистов, позволяя им писать эффективный, чистый и лаконичный код.

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

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

+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+       +------+   +------+   +---+   +-------+

Приведённый выше процесс преобразуется в код Java следующим образом:

List<Integer> transactionsIds = 
widgets.stream()
             .filter(b -> b.getColor() == RED)
             .sorted((x,y) -> x.getWeight() - y.getWeight())
             .mapToInt(Widget::getWeight)
             .sum();

Что такое Stream?

Stream — это очередь элементов из источника данных, которая поддерживает агрегатные операции.

  • Элементы — это объекты определённого типа, образующие очередь. Stream в Java не хранит элементы; вместо этого он вычисляет их по требованию.
  • Источник данныхИсточник потока. Это может быть коллекция, массив, канал ввода-вывода, генератор и т. д.
  • Агрегатные операцииОперации, похожие на SQL-запросы, такие как filter, map, reduce, find, match, sorted и т. д.

В отличие от предыдущих операций Collection, операции Stream имеют две основные характеристики:

  • КонвейеризацияКонвейеризация: все промежуточные операции возвращают сам объект Stream. Это позволяет объединять несколько операций в конвейер, как в fluent-стиле. Это позволяет оптимизировать операции, например, за счёт ленивых вычислений и короткого замыкания.
  • Внутренняя итерацияВнутренняя итерация: раньше обход коллекции выполнялся через Iterator или For-Each, с явной итерацией вне коллекции, что называется внешней итерацией. Stream предоставляет метод внутренней итерации, реализованный через паттерн Visitor.

Генерация Stream

В Java 8 интерфейс коллекции имеет два метода для генерации потоков:

  • stream()− Создает последовательный поток для коллекции.

  • parallelStream()− Создает параллельный поток для коллекции.

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

forEach

Stream предоставляет новый метод 'forEach' для перебора каждого элемента данных в потоке. Следующий фрагмент кода использует forEach для вывода 10 случайных чисел:

Random random = new Random(); random.ints().limit(10).forEach(System.out::println);

map

Метод map используется для сопоставления каждого элемента с соответствующим результатом. Следующий фрагмент кода использует map для вывода квадратов элементов:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); //Получить соответствующие квадраты чисел List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

filter

Метод filter используется для фильтрации элементов по заданному условию. Следующий фрагмент кода использует метод filter для отфильтровывания пустых строк:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); //Получить количество пустых строк long count = strings.stream().filter(string -> string.isEmpty()).count();

limit

Метод limit используется для получения потока с указанным количеством. Следующий фрагмент кода использует метод limit для вывода 10 элементов данных:

Random random = new Random(); random.ints().limit(10).forEach(System.out::println);

sorted

Метод sorted используется для сортировки потока. Следующий фрагмент кода использует метод sorted для сортировки 10 выводимых случайных чисел:

Random random = new Random(); random.ints().limit(10).sorted().forEach(System.out::println);

Параллельная программа

parallelStream — альтернативный метод для параллельной обработки потоков. В следующем примере мы используем parallelStream для вывода количества пустых строк:

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); //Получить количество пустых строк long count = strings.parallelStream().filter(string -> string.isEmpty()).count();

Мы можем легко переключаться между последовательным и параллельным выполнением.


Collectors

Класс Collectors реализует множество операций сведения, таких как преобразование потоков в коллекции и агрегирование элементов. Collectors можно использовать для возврата списков или строк:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println("Отфильтрованный список:" + filtered); String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("Объединённые строки:" + mergedString);

Статистика

Кроме того, некоторые коллекторы, которые создают статистические результаты, также очень полезны. Они в основном используются с примитивными типами, такими как int, double и long, и могут использоваться для получения статистических результатов, подобных следующим.

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("Наибольшее число в списке:" + stats.getMax()); System.out.println("Наименьшее число в списке:" + stats.getMin()); System.out.println("Сумма всех чисел:" + stats.getSum()); System.out.println("Среднее:" + stats.getAverage());

Полный пример Stream

Поместите следующий код в файл Java8Tester.java:

Файл Java8Tester.java

import java.util.ArrayList; import java.util.Arrays; import java.util.IntSummaryStatistics; import java.util.List; import java.util.Random; import java.util.stream.Collectors; import java.util.Map; public class Java8Tester { public static void main(String args[]){ System.out.println("Использование Java 7:"); //Подсчет пустых строк List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); System.out.println("Список:" +strings); long count = getCountEmptyStringUsingJava7(strings); System.out.println("Количество пустых строк:" + count); count = getCountLength3UsingJava7(strings); System.out.println("Количество строк длиной 3:" + count); //Удалить пустые строки List<String> filtered = deleteEmptyStringsUsingJava7(strings); System.out.println("Отфильтрованный список:" + filtered); //Удалить пустые строки и объединить их запятыми String mergedString = getMergedStringUsingJava7(strings,", "); System.out.println("Объединённые строки:" + mergedString); List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); //Получить квадраты элементов списка List<Integer> squaresList = getSquares(numbers); System.out.println("Список квадратов чисел:" + squaresList); List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19); System.out.println("Список:" +integers); System.out.println("Наибольшее число в списке:" + getMax(integers)); System.out.println("Наименьшее число в списке:" + getMin(integers)); System.out.println("Сумма всех чисел:" + getSum(integers)); System.out.println("Среднее:" + getAverage(integers)); System.out.println("Случайные числа:"); //Вывести 10 случайных чисел Random random = new Random(); for(int i=0; i < 10; i++){ System.out.println(random.nextInt()); } System.out.println("Использование Java 8:"); System.out.println("Список:" +strings); count = strings.stream().filter(string->string.isEmpty()).count(); System.out.println("Количество пустых строк:" + count); count = strings.stream().filter(string -> string.length() == 3).count(); System.out.println("Количество строк длиной 3:" + count); filtered = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.toList()); System.out.println("Отфильтрованный список:" + filtered); mergedString = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("Объединённые строки:" + mergedString); squaresList = numbers.stream().map( i ->i*i).distinct().collect(Collectors.toList()); System.out.println("Список квадратов: " + squaresList); System.out.println("List:" +integers); IntSummaryStatistics stats = integers.stream().mapToInt((x) ->x).summaryStatistics(); System.out.println("Наибольшее число в списке:" + stats.getMax()); System.out.println("Наименьшее число в списке:" + stats.getMin()); System.out.println("Сумма всех чисел:" + stats.getSum()); System.out.println("Среднее:" + stats.getAverage()); System.out.println("Случайные числа:"); random.ints().limit(10).sorted().forEach(System.out::println); //Параллельная обработка count = strings.parallelStream().filter(string -> string.isEmpty()).count(); System.out.println("Количество пустых строк:" + count); } private static int getCountEmptyStringUsingJava7(List<String> strings){ int count = 0; for(String string: strings){ if(string.isEmpty()){ count++; } } return count; } private static int getCountLength3UsingJava7(List<String> strings){ int count = 0; for(String string: strings){ if(string.length() == 3){ count++; } } return count; } private static List<String> deleteEmptyStringsUsingJava7(List<String> strings){ List<String> filteredList = new ArrayList<String>(); for(String string: strings){ if(!string.isEmpty()){ filteredList.add(string); } } return filteredList; } private static String getMergedStringUsingJava7(List<String> strings, String separator){ StringBuilder stringBuilder = new StringBuilder(); for(String string: strings){ if(!string.isEmpty()){ stringBuilder.append(string); stringBuilder.append(separator); } } String mergedString = stringBuilder.toString(); return mergedString.substring(0, mergedString.length()-2); } private static List<Integer> getSquares(List<Integer> numbers){ List<Integer> squaresList = new ArrayList<Integer>(); for(Integer number: numbers){ Integer square = new Integer(number.intValue() * number.intValue()); if(!squaresList.contains(square)){ squaresList.add(square); } } return squaresList; } private static int getMax(List<Integer> numbers){ int max = numbers.get(0); for(int i=1;i < numbers.size();i++){ Integer number = numbers.get(i); if(number.intValue() > max){ max = number.intValue(); } } return max; } private static int getMin(List<Integer> numbers){ int min = numbers.get(0); for(int i=1;i < numbers.size();i++){ Integer number = numbers.get(i); if(number.intValue() < min){ min = number.intValue(); } } return min; } private static int getSum(List numbers){ int sum = (int)(numbers.get(0)); for(int i=1;i < numbers.size();i++){ sum += (int)numbers.get(i); } return sum; } private static int getAverage(List<Integer> numbers){ return getSum(numbers) / numbers.size(); } }

Выполнение приведённого выше скрипта даёт следующий вывод:

$ javac Java8Tester.java 
$ java Java8Tester
使用 Java 7: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符数量为: 2
字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
平方数列表: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 85
平均数 : 9
随机数: 
-393170844
-963842252
447036679
-1043163142
-881079698
221586850
-1101570113
576190039
-1045184578
1647841045
使用 Java 8: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符串数量为: 2
字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
Squares List: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 85
平均数 : 9.444444444444445
随机数: 
-1743813696
-1301974944
-1299484995
-779981186
136544902
555792023
1243315896
1264920849
1472077135
1706423674
空字符串的数量为: 2

Java 8 新特性Новые возможности Java 8

Другие расширения