Поиск…


Вступление

Stream представляет последовательность элементов и поддерживает различные виды операций для выполнения вычислений по этим элементам. С Java 8 интерфейс Collection имеет два метода для генерации Stream : stream() и parallelStream() . Операции Stream являются промежуточными или конечными. Промежуточные операции возвращают Stream поэтому несколько промежуточных операций могут быть привязаны до того, как Stream будет закрыт. Операции с терминалом либо недействительны, либо возвращают результат без потока.

Синтаксис

  • collection.stream ()
  • Arrays.stream (массив)
  • Stream.iterate (firstValue, currentValue -> nextValue)
  • Stream.generate (() -> value)
  • Stream.of (elementOfT [, elementOfT, ...])
  • Stream.empty ()
  • StreamSupport.stream (iterable.spliterator (), false)

Использование потоков

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

Пример: ( см. Работу над Ideone )

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");

fruitStream.filter(s -> s.contains("a"))
           .map(String::toUpperCase)
           .sorted()
           .forEach(System.out::println);

Выход:

ЯБЛОКО
БАНАН
ОРАНЖЕВЫЙ
ГРУША

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

  1. Создайте Stream<String> содержащий упорядоченный упорядоченный Stream элементов String для фруктов, используя статический заводский метод Stream.of(values) .

  2. Операция filter() сохраняет только элементы, которые соответствуют заданному предикату (элементы, которые при проверке предикатом возвращают true). В этом случае он сохраняет элементы, содержащие "a" . Предикат задается как лямбда-выражение .

  3. Операция map() преобразует каждый элемент с помощью заданной функции, называемой mapper. В этом случае каждая String фруктов преобразуется в его строчную версию String с использованием ссылки на метод String::toUppercase .

    Обратите внимание, что операция map() вернет поток с другим общим типом, если функция сопоставления возвращает тип, отличный от его входного параметра. Например, в Stream<String> вызов .map(String::isEmpty) возвращает Stream<Boolean>

  4. Операция sorted() сортирует элементы Stream соответствии с их естественным упорядочением (лексикографически, в случае String ).

  5. Наконец, операция forEach(action) выполняет действие, действующее на каждый элемент Stream , передавая его потребителю . В этом примере каждый элемент просто печатается на консоли. Эта операция является терминальной операцией, что делает невозможным ее повторное использование.

    Обратите внимание, что операции, определенные в Stream , выполняются из- за операции терминала. Без операции с терминалом поток не обрабатывается. Потоки нельзя использовать повторно. После вызова терминальной операции объект Stream становится непригодным.

Цепные операции

Операции (как показано выше) соединены вместе, чтобы сформировать то, что можно рассматривать как запрос данных.


Закрытие потоков

Обратите внимание, что Stream как правило, не нужно закрывать. Требуется только закрыть потоки, которые работают на каналах ввода-вывода. Большинство типов Stream не работают на ресурсах и поэтому не требуют закрытия.

Интерфейс Stream расширяет AutoCloseable . Потоки могут быть закрыты вызовом метода close или с помощью операторов try-with-resource.

Пример использования, когда Stream должен быть закрыт, - это когда вы создаете Stream строк из файла:

try (Stream<String> lines = Files.lines(Paths.get("somePath"))) {
    lines.forEach(System.out::println);
}

Интерфейс Stream также объявляет метод Stream.onClose() который позволяет вам регистрировать обработчики Runnable которые будут вызываться, когда поток закрыт. Пример использования - это то, где код, который создает поток, должен знать, когда он потребляется, чтобы выполнить некоторую очистку.

public Stream<String>streamAndDelete(Path path) throws IOException {
    return Files.lines(path).onClose(() -> someClass.deletePath(path));
}

Обработчик запуска будет выполняться только в том случае, если метод close() вызывается, явно или неявно, с помощью инструкции try-with-resources.


Порядок обработки

Обработка объекта Stream может быть последовательной или параллельной .

В последовательном режиме элементы обрабатываются в порядке источника Stream . Если Stream упорядочен (например, реализация SortedMap или List ), то гарантируется, что обработка будет соответствовать порядку источника. В других случаях, однако, следует соблюдать осторожность, чтобы не зависеть от порядка (см. keySet() порядок итераций Java HashMap keySet() ? ).

Пример:

List<Integer> integerList = Arrays.asList(0, 1, 2, 3, 42); 

// sequential 
long howManyOddNumbers = integerList.stream()
                                    .filter(e -> (e % 2) == 1)
                                    .count(); 

System.out.println(howManyOddNumbers); // Output: 2

Живой на Ideone

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

Если вызывается несколько методов в последовательном Stream , не каждый метод должен быть вызван. Например, если Stream фильтруется и количество элементов сводится к единице, последующий вызов метода, такого как sort , не будет выполняться. Это может увеличить производительность последовательного Stream - оптимизация, которая невозможна при параллельном Stream .

Пример:

// parallel
long howManyOddNumbersParallel = integerList.parallelStream()
                                            .filter(e -> (e % 2) == 1)
                                            .count();

System.out.println(howManyOddNumbersParallel); // Output: 2

Живой на Ideone


Отличия от контейнеров (или коллекций )

Хотя некоторые действия могут выполняться как в контейнерах, так и в потоках, они в конечном итоге служат различным целям и поддерживают разные операции. Контейнеры больше сосредоточены на том, как хранятся элементы и как эти элементы могут быть доступны эффективно. С другой стороны, Stream не обеспечивает прямого доступа и манипулирования его элементами; он больше предназначен для группы объектов как коллективного объекта и выполняет операции над этим объектом в целом. Stream и Collection - это отдельные абстракции высокого уровня для этих различных целей.

Соберите элементы потока в коллекцию

Собирайте с помощью toList() и toSet()

Элементы из Stream можно легко собрать в контейнер с помощью операции Stream.collect :

System.out.println(Arrays
    .asList("apple", "banana", "pear", "kiwi", "orange")
    .stream()
    .filter(s -> s.contains("a"))
    .collect(Collectors.toList())
);
// prints: [apple, banana, pear, orange]

Другие экземпляры коллекции, такие как Set , могут быть сделаны с использованием других встроенных методов Collectors . Например, Collectors.toSet() собирает элементы Stream в Set .


Явный контроль над реализацией List или Set

Согласно документации Collectors#toList() и Collectors#toSet() , нет никаких гарантий по типу, изменчивости, сериализуемости или потокобезопасности возвращаемого List или Set .

Для явного контроля над возвращаемой реализацией вместо этого можно использовать Collectors#toCollection(Supplier) , где данный поставщик возвращает новую и пустую коллекцию.

// syntax with method reference
System.out.println(strings
        .stream()
        .filter(s -> s != null && s.length() <= 3)
        .collect(Collectors.toCollection(ArrayList::new))
);

// syntax with lambda
System.out.println(strings
        .stream()
        .filter(s -> s != null && s.length() <= 3)
        .collect(Collectors.toCollection(() -> new LinkedHashSet<>()))
);

Сбор элементов с помощью toMap

Коллекционер накапливает элементы на карте, где ключ - это идентификатор студента, а значение - значение ученика.

  List<Student> students = new ArrayList<Student>(); 
    students.add(new Student(1,"test1"));
    students.add(new Student(2,"test2"));
    students.add(new Student(3,"test3"));
    
    Map<Integer, String> IdToName = students.stream()
        .collect(Collectors.toMap(Student::getId, Student::getName));
    System.out.println(IdToName);

Выход :

{1=test1, 2=test2, 3=test3}

У Collectors.toMap есть другая реализация Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) . Функция mergeFunction используется в основном для выбора нового значения или сохранения старого значения, если ключ повторяется при добавлении нового члена в Map из списка.

Функция mergeFunction часто выглядит так: (s1, s2) -> s1 чтобы сохранить значение, соответствующее повторенному ключу, или (s1, s2) -> s2 чтобы добавить новое значение для повторного ключа.

Сбор элементов на карте коллекций

Пример: от ArrayList до Map <String, List <>>

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

    List<Student> list = new ArrayList<>();
    list.add(new Student("Davis", SUBJECT.MATH, 35.0));
    list.add(new Student("Davis", SUBJECT.SCIENCE, 12.9));
    list.add(new Student("Davis", SUBJECT.GEOGRAPHY, 37.0));

    list.add(new Student("Sascha", SUBJECT.ENGLISH, 85.0));
    list.add(new Student("Sascha", SUBJECT.MATH, 80.0));
    list.add(new Student("Sascha", SUBJECT.SCIENCE, 12.0));
    list.add(new Student("Sascha", SUBJECT.LITERATURE, 50.0));

    list.add(new Student("Robert", SUBJECT.LITERATURE, 12.0));

    Map<String, List<SUBJECT>> map = new HashMap<>();
    list.stream().forEach(s -> {
                map.computeIfAbsent(s.getName(), x -> new ArrayList<>()).add(s.getSubject());
            });
    System.out.println(map);

Выход:

{ Robert=[LITERATURE], 
Sascha=[ENGLISH, MATH, SCIENCE, LITERATURE], 
Davis=[MATH, SCIENCE, GEOGRAPHY] }

Пример: от ArrayList до Map <String, Map <>>

    List<Student> list = new ArrayList<>();
    list.add(new Student("Davis", SUBJECT.MATH, 1, 35.0));
    list.add(new Student("Davis", SUBJECT.SCIENCE, 2, 12.9));
    list.add(new Student("Davis", SUBJECT.MATH, 3, 37.0));
    list.add(new Student("Davis", SUBJECT.SCIENCE, 4, 37.0));

    list.add(new Student("Sascha", SUBJECT.ENGLISH, 5, 85.0));
    list.add(new Student("Sascha", SUBJECT.MATH, 1, 80.0));
    list.add(new Student("Sascha", SUBJECT.ENGLISH, 6, 12.0));
    list.add(new Student("Sascha", SUBJECT.MATH, 3, 50.0));

    list.add(new Student("Robert", SUBJECT.ENGLISH, 5, 12.0));

    Map<String, Map<SUBJECT, List<Double>>> map = new HashMap<>();

    list.stream().forEach(student -> {
        map.computeIfAbsent(student.getName(), s -> new HashMap<>())
                .computeIfAbsent(student.getSubject(), s -> new ArrayList<>())
                .add(student.getMarks());
    });

    System.out.println(map);

Выход:

{ Robert={ENGLISH=[12.0]}, 
Sascha={MATH=[80.0, 50.0], ENGLISH=[85.0, 12.0]}, 
Davis={MATH=[35.0, 37.0], SCIENCE=[12.9, 37.0]} }

Чит-лист

Цель Код
Сбор в List Collectors.toList()
Собирать в ArrayList с заранее заданным размером Collectors.toCollection(() -> new ArrayList<>(size))
Собрать Set Collectors.toSet()
Собрать в Set с лучшей итерационной производительностью Collectors.toCollection(() -> new LinkedHashSet<>())
Собрать в нечувствительный к регистру Set<String> Collectors.toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER))
Соберите в EnumSet<AnEnum> (наилучшая производительность для перечислений) Collectors.toCollection(() -> EnumSet.noneOf(AnEnum.class))
Соберите Map<K,V> с уникальными ключами Collectors.toMap(keyFunc,valFunc)
Карта MyObject.getter () для уникального объекта MyObject Collectors.toMap(MyObject::getter, Function.identity())
Карта MyObject.getter () для нескольких объектов MyObjects Collectors.groupingBy(MyObject::getter)

Бесконечные потоки

Можно создать Stream , который не заканчивается. Вызов метода терминала в бесконечном Stream приводит к тому, что Stream вводит бесконечный цикл. Метод limit Stream может использоваться для ограничения количества терминов Stream которые обрабатывает Java.

В этом примере генерируется Stream всех натуральных чисел, начиная с номера 1. Каждый последующий член Stream является одним выше предыдущего. Вызывая метод ограничения этого Stream , рассматриваются и печатаются только первые пять членов Stream .

// Generate infinite stream - 1, 2, 3, 4, 5, 6, 7, ...
IntStream naturalNumbers = IntStream.iterate(1, x -> x + 1);

// Print out only the first 5 terms
naturalNumbers.limit(5).forEach(System.out::println);

Выход:

1
2
3
4
5


Другой способ генерации бесконечного потока - использовать метод Stream.generate . Этот метод принимает лямбда типа Поставщик .

// Generate an infinite stream of random numbers
Stream<Double> infiniteRandomNumbers = Stream.generate(Math::random);

// Print out only the first 10 random numbers
infiniteRandomNumbers.limit(10).forEach(System.out::println);

Потребительские потоки

Stream будет пройден только тогда, когда есть операция терминала , например count() , collect() или forEach() . В противном случае операция Stream не будет выполнена.

В следующем примере терминальная операция не добавляется в Stream , поэтому операция filter() не будет вызываться и не будет выводиться вывод, потому что peek() НЕ является терминальной операцией .

IntStream.range(1, 10).filter(a -> a % 2 == 0).peek(System.out::println);

Живой на Ideone

Это последовательность Stream с действительной работой терминала , поэтому производится выход.

Вы также можете использовать forEach вместо peek :

IntStream.range(1, 10).filter(a -> a % 2 == 0).forEach(System.out::println); 

Живой на Ideone

Выход:

2
4
6
8

После выполнения операции терминала Stream потребляется и не может быть повторно использован.


Хотя данный объект потока нельзя использовать повторно, легко создать многоразовый Iterable который делегирует потоковый конвейер. Это может быть полезно для возвращения измененного представления живого набора данных без необходимости собирать результаты во временную структуру.

List<String> list = Arrays.asList("FOO", "BAR");
Iterable<String> iterable = () -> list.stream().map(String::toLowerCase).iterator();

for (String str : iterable) {
    System.out.println(str);
}
for (String str : iterable) {
    System.out.println(str);
}

Выход:

Foo
бар
Foo
бар

Это работает, потому что Iterable объявляет один абстрактный метод Iterator<T> iterator() . Это делает его эффективным функциональным интерфейсом, реализованным лямбдой, которая создает новый поток для каждого вызова.


В общем, Stream работает, как показано на следующем изображении:

Операция потока

ПРИМЕЧАНИЕ . Проверки аргументов всегда выполняются даже без операции терминала :

try {
    IntStream.range(1, 10).filter(null);
} catch (NullPointerException e) {
    System.out.println("We got a NullPointerException as null was passed as an argument to filter()");
}

Живой на Ideone

Выход:

Мы получили исключение NullPointerException, поскольку значение null было передано как аргумент filter ()

Создание карты частоты

Сборщик groupingBy(classifier, downstream) позволяет собирать элементы Stream в Map , классифицируя каждый элемент в группе и выполняя нисходящую операцию над элементами, классифицированными в той же группе.

Классическим примером этого принципа является использование Map для подсчета появления элементов в Stream . В этом примере классификатор - это просто функция идентификации, которая возвращает элемент as-is. Операция downstream подсчитывает количество равных элементов, используя counting() .

Stream.of("apple", "orange", "banana", "apple")
      .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
      .entrySet()
      .forEach(System.out::println);

Операция нисходящего потока сама является сборщиком ( Collectors.counting() ), который работает с элементами типа String и производит результат типа Long . Результатом вызова метода collect является Map<String, Long> .

Это приведет к следующему результату:

банан = 1
оранжевый = 1
яблоко = 2

Параллельный поток

Примечание. Перед тем, как решить, какой Stream использовать, пожалуйста, взгляните на поведение ParallelStream vs Sequential Stream .

Если вы хотите одновременно выполнять операции Stream , вы можете использовать любой из этих способов.

List<String> data = Arrays.asList("One", "Two", "Three", "Four", "Five");
Stream<String> aParallelStream = data.stream().parallel();

Или же:

Stream<String> aParallelStream = data.parallelStream();

Чтобы выполнить операции, определенные для параллельного потока, вызовите оператор терминала:

aParallelStream.forEach(System.out::println);

(Возможен) выход из параллельного Stream :

Три
четыре
Один
Два
5

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

Эффективное воздействие

В случае задействования сети параллельный Stream s может ухудшить общую производительность приложения, поскольку все параллельные Stream используют общий пул потоков fork-join для сети.

С другой стороны, параллельный Stream s может значительно повысить производительность во многих других случаях, в зависимости от количества доступных ядер в текущем CPU на данный момент.

Преобразование потока необязательно в поток значений

Возможно, вам потребуется конвертировать Stream излучающий Optional для Stream значений, испускающий только значения из существующего Optional . (т. е. без null значения и не имея дело с Optional.empty() ).

 Optional<String> op1 = Optional.empty();
 Optional<String> op2 = Optional.of("Hello World");

 List<String> result = Stream.of(op1, op2)
                             .filter(Optional::isPresent)
                             .map(Optional::get)
                             .collect(Collectors.toList());

 System.out.println(result); //[Hello World]

Создание потока

Все java Collection<E> имеют методы stream() и parallelStream() из которых можно построить Stream<E> :

Collection<String> stringList = new ArrayList<>();
Stream<String> stringStream = stringList.parallelStream();

Stream<E> может быть создан из массива с использованием одного из двух методов:

String[] values = { "aaa", "bbbb", "ddd", "cccc" };
Stream<String> stringStream = Arrays.stream(values);
Stream<String> stringStreamAlternative = Stream.of(values);

Разница между Arrays.stream() и Stream.of() заключается в том, что Stream.of() имеет параметр varargs, поэтому его можно использовать как:

Stream<Integer> integerStream = Stream.of(1, 2, 3);

Есть также примитивные Stream которые вы можете использовать. Например:

IntStream intStream = IntStream.of(1, 2, 3);
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0);

Эти примитивные потоки также могут быть построены с использованием Arrays.stream() :

IntStream intStream = Arrays.stream(new int[]{ 1, 2, 3 });

Можно создать Stream из массива с указанным диапазоном.

int[] values= new int[]{1, 2, 3, 4, 5};
IntStream intStram = Arrays.stream(values, 1, 3);

Обратите внимание, что любой примитивный поток может быть преобразован в поток с коротким типом, используя метод boxed :

Stream<Integer> integerStream = intStream.boxed();

Это может быть полезно в некоторых случаях, если вы хотите собирать данные, поскольку примитивный поток не имеет никакого метода collect который принимает Collector качестве аргумента.

Повторное использование промежуточных операций цепочки потоков

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

Supplier<Stream<String>> streamSupplier = () -> Stream.of("apple", "banana","orange", "grapes", "melon","blueberry","blackberry")
.map(String::toUpperCase).sorted();

  streamSupplier.get().filter(s ->   s.startsWith("A")).forEach(System.out::println);

// APPLE

  streamSupplier.get().filter(s -> s.startsWith("B")).forEach(System.out::println);

  // BANANA
  // BLACKBERRY
  // BLUEBERRY

int[] массивы могут быть преобразованы в List<Integer> с использованием потоков

int[] ints = {1,2,3};
List<Integer> list = IntStream.of(ints).boxed().collect(Collectors.toList());

Поиск статистики о числовых потоках

Java 8 предоставляет классы, называемые IntSummaryStatistics , DoubleSummaryStatistics и LongSummaryStatistics которые предоставляют объект состояния для сбора статистики, такой как count , min , max , sum и average .

Java SE 8
List<Integer> naturalNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = naturalNumbers.stream()
                                           .mapToInt((x) -> x)
                                           .summaryStatistics();
System.out.println(stats);

Это приведет к:

Java SE 8
IntSummaryStatistics{count=10, sum=55, min=1, max=10, average=5.500000}

Получить фрагмент потока

Пример. Получите Stream из 30 элементов, содержащий от 21 до 50 (включительно) элемент коллекции.

final long n = 20L; // the number of elements to skip
final long maxSize = 30L; // the number of elements the stream should be limited to
final Stream<T> slice = collection.stream().skip(n).limit(maxSize);

Заметки:

  • IllegalArgumentException выбрасывается, если n отрицательно или maxSize отрицательный
  • обе skip(long) и limit(long) промежуточные операции
  • если поток содержит менее n элементов, то skip(n) возвращает пустой поток
  • как skip(long) и limit(long) являются дешевыми операциями на последовательных поточных трубопроводах, но могут быть довольно дорогими на упорядоченных параллельных трубопроводах

Конкатенация потоков

Объявление переменной для примеров:

Collection<String> abc = Arrays.asList("a", "b", "c");
Collection<String> digits = Arrays.asList("1", "2", "3");
Collection<String> greekAbc = Arrays.asList("alpha", "beta", "gamma");

Пример 1 - Объединение двух Stream s

final Stream<String> concat1 = Stream.concat(abc.stream(), digits.stream());

concat1.forEach(System.out::print);
// prints: abc123

Пример 2 - Конкатенация более двух Stream s

final Stream<String> concat2 = Stream.concat(
    Stream.concat(abc.stream(), digits.stream()),
    greekAbc.stream());

System.out.println(concat2.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma

В качестве альтернативы , чтобы упростить вложенный concat() синтаксис Stream ы также могут быть объединены с flatMap() :

final Stream<String> concat3 = Stream.of(
    abc.stream(), digits.stream(), greekAbc.stream())
    .flatMap(s -> s);
    // or `.flatMap(Function.identity());` (java.util.function.Function)

System.out.println(concat3.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma

Будьте осторожны при построении Stream из повторной конкатенации, потому что доступ к элементу глубоко конкатенированного Stream может привести к глубоким цепочкам вызовов или даже StackOverflowException .

IntStream to String

Java не имеет Char Stream , поэтому при работе со String s и построении Stream of Character s, опция должна получить IntStream кодовых точек с использованием String.codePoints() . Таким образом, IntStream можно получить следующим образом:

public IntStream stringToIntStream(String in) {
  return in.codePoints();
}

Это немного больше, чтобы сделать конверсию другим способом, то есть IntStreamToString. Это можно сделать следующим образом:

public String intStreamToString(IntStream intStream) {
  return intStream.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
}

Сортировка по потоку

List<String> data = new ArrayList<>();
data.add("Sydney");
data.add("London");
data.add("New York");
data.add("Amsterdam");
data.add("Mumbai");
data.add("California");

System.out.println(data);

List<String> sortedData = data.stream().sorted().collect(Collectors.toList());

System.out.println(sortedData);

Выход:

[Sydney, London, New York, Amsterdam, Mumbai, California]
[Amsterdam, California, London, Mumbai, New York, Sydney]

Также возможно использовать другой механизм сравнения, так как существует перегруженная sorted версия, которая принимает в качестве аргумента компаратор.

Кроме того, вы можете использовать выражение лямбда для сортировки:

List<String> sortedData2 = data.stream().sorted((s1,s2) -> s2.compareTo(s1)).collect(Collectors.toList());

Это вывело бы [Sydney, New York, Mumbai, London, California, Amsterdam]

Вы можете использовать Comparator.reverseOrder() чтобы иметь компаратор, который налагает reverse сторону естественного упорядочения.

List<String> reverseSortedData = data.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());

Потоки примитивов

Java предоставляет специализированные Stream s для трех типов примитивов IntStream (для int s), LongStream (для long s) и DoubleStream (для double s). Помимо оптимизации реализаций их соответствующих примитивов, они также предоставляют несколько конкретных терминальных методов, как правило, для математических операций. Например:

IntStream is = IntStream.of(10, 20, 30);
double average = is.average().getAsDouble(); // average is 20.0

Соберите результаты потока в массив

Аналоговый, чтобы получить коллекцию для Stream методом collect() , массив может быть получен методом Stream.toArray() :

List<String> fruits = Arrays.asList("apple", "banana", "pear", "kiwi", "orange");

String[] filteredFruits = fruits.stream()
    .filter(s -> s.contains("a"))
    .toArray(String[]::new);     

// prints: [apple, banana, pear, orange]
System.out.println(Arrays.toString(filteredFruits));

String[]::new - это особый вид ссылки на метод: ссылка на конструктор.

Поиск первого элемента, который соответствует предикату

Можно найти первый элемент Stream который соответствует условию.

В этом примере мы найдем первое Integer , квадрат которого превышает 50000 .

IntStream.iterate(1, i -> i + 1) // Generate an infinite stream 1,2,3,4...
    .filter(i -> (i*i) > 50000) // Filter to find elements where the square is >50000
    .findFirst(); // Find the first filtered element

Это выражение вернет OptionalInt с результатом.

Обратите внимание, что с бесконечным Stream Java будет проверять каждый элемент до тех пор, пока не найдет результат. С конечным Stream , если Java исчерпывает элементы, но все равно не может найти результат, он возвращает пустой OptionalInt .

Использование IntStream для перебора индексов

Stream s элементов обычно не позволяет получить доступ к значению индекса текущего элемента. Чтобы перебирать массив или ArrayList , имея доступ к индексам, используйте IntStream.range(start, endExclusive) .

String[] names = { "Jon", "Darin", "Bauke", "Hans", "Marc" };

IntStream.range(0, names.length)
    .mapToObj(i -> String.format("#%d %s", i + 1, names[i]))
    .forEach(System.out::println);

Метод range(start, endExclusive) возвращает другой ÌntStream а mapToObj(mapper) возвращает поток String .

Выход:

# 1 Jon
# 2 Дарин
# 3 Бауке
# 4 Ханс
# 5 Марк

Это очень похоже на использование нормального for цикла со счетчиком, но с выгодой конвейерных и распараллеливания:

for (int i = 0; i < names.length; i++) {
    String newName = String.format("#%d %s", i + 1, names[i]);
    System.out.println(newName);
}

Сгладить потоки с помощью flatMap ()

Stream предметов, которые в свою очередь могут быть потоковыми, может быть сплющен в один непрерывный Stream :

Массив списка элементов можно преобразовать в один список.

List<String> list1 =  Arrays.asList("one", "two");
      List<String> list2 =  Arrays.asList("three","four","five");
      List<String> list3 =  Arrays.asList("six");
          List<String> finalList = Stream.of(list1, list2, list3).flatMap(Collection::stream).collect(Collectors.toList());
System.out.println(finalList);

// [one, two, three, four, five, six]

Карта, содержащая Список элементов как значений, может быть сглажена в комбинированный список

Map<String, List<Integer>> map = new LinkedHashMap<>();
map.put("a", Arrays.asList(1, 2, 3));
map.put("b", Arrays.asList(4, 5, 6));

List<Integer> allValues = map.values() // Collection<List<Integer>>
        .stream()                      // Stream<List<Integer>>
        .flatMap(List::stream)         // Stream<Integer>
        .collect(Collectors.toList());

System.out.println(allValues);
// [1, 2, 3, 4, 5, 6]

List Map может быть сплющен в один непрерывный Stream

List<Map<String, String>> list = new ArrayList<>();
Map<String,String> map1 = new HashMap();
map1.put("1", "one");
map1.put("2", "two");

Map<String,String> map2 = new HashMap();
map2.put("3", "three");
map2.put("4", "four");
list.add(map1);
list.add(map2);


Set<String> output= list.stream()  //  Stream<Map<String, String>>
    .map(Map::values)              // Stream<List<String>>
    .flatMap(Collection::stream)   // Stream<String>
    .collect(Collectors.toSet());  //Set<String>
// [one, two, three,four]

Создание карты на основе потока

Простой случай без дубликатов ключей

Stream<String> characters = Stream.of("A", "B", "C");

Map<Integer, String> map = characters
            .collect(Collectors.toMap(element -> element.hashCode(), element -> element));
// map = {65=A, 66=B, 67=C}

Чтобы сделать вещи более декларативными, мы можем использовать статический метод в Function интерфейсе - Function.identity() . Мы можем заменить этот element -> element лямбда- element -> element Function.identity() .

Случай, когда могут быть дубликаты ключей

В javadoc для Collectors.toMap указано:

Если отображаемые ключи содержат дубликаты (в соответствии с Object.equals(Object) ), при выполнении операции сбора генерируется IllegalStateException . Если отображаемые ключи могут иметь дубликаты, toMap(Function, Function, BinaryOperator) используйте toMap(Function, Function, BinaryOperator) .

Stream<String> characters = Stream.of("A", "B", "B", "C");

Map<Integer, String> map = characters
            .collect(Collectors.toMap(
                element -> element.hashCode(),
                element -> element,
                (existingVal, newVal) -> (existingVal + newVal)));

// map = {65=A, 66=BB, 67=C}

BinaryOperator передается в Collectors.toMap(...) генерирует значение, которое нужно сохранить в случае столкновения. Оно может:

  • возвращает старое значение, так что первое значение в потоке имеет приоритет,
  • вернуть новое значение, так что последнее значение в потоке имеет приоритет или
  • объединить старые и новые значения

Группировка по значению

Вы можете использовать Collectors.groupingBy когда вам нужно выполнить эквивалент операции каскадирования базы данных «по группам». Чтобы проиллюстрировать это, создается следующая карта, в которой имена людей сопоставляются с фамилиями:

List<Person> people = Arrays.asList(
    new Person("Sam", "Rossi"),
    new Person("Sam", "Verdi"),
    new Person("John", "Bianchi"),
    new Person("John", "Rossi"),
    new Person("John", "Verdi")
);

Map<String, List<String>> map = people.stream()
        .collect(
                // function mapping input elements to keys
                Collectors.groupingBy(Person::getName, 
                // function mapping input elements to values,
                // how to store values
                Collectors.mapping(Person::getSurname, Collectors.toList()))
        );

// map = {John=[Bianchi, Rossi, Verdi], Sam=[Rossi, Verdi]}

Живой на Ideone

Генерация случайных строк с использованием потоков

Иногда бывает полезно создавать случайные Strings , возможно, как идентификатор сеанса для веб-службы или начальный пароль после регистрации для приложения. Это может быть легко достигнуто с помощью Stream s.

Сначала нам нужно инициализировать генератор случайных чисел. Чтобы повысить безопасность созданных String s, рекомендуется использовать SecureRandom .

Примечание . Создание SecureRandom довольно дорогое, поэтому лучше всего сделать это только один раз и время от времени вызывать один из методов setSeed() для его setSeed() .

private static final SecureRandom rng = new SecureRandom(SecureRandom.generateSeed(20)); 
//20 Bytes as a seed is rather arbitrary, it is the number used in the JavaDoc example

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

//returns true for all chars in 0-9, a-z and A-Z
boolean useThisCharacter(char c){
    //check for range to avoid using all unicode Letter (e.g. some chinese symbols)
    return c >= '0' && c <= 'z' && Character.isLetterOrDigit(c);
}

Затем мы сможем использовать RNG для генерации случайной строки определенной длины, содержащей кодировку, которая передает нашу проверку useThisCharacter .

public String generateRandomString(long length){
    //Since there is no native CharStream, we use an IntStream instead
    //and convert it to a Stream<Character> using mapToObj.
    //We need to specify the boundaries for the int values to ensure they can safely be cast to char
    Stream<Character> randomCharStream = rng.ints(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT).mapToObj(i -> (char)i).filter(c -> this::useThisCharacter).limit(length);

    //now we can use this Stream to build a String utilizing the collect method.
    String randomString = randomCharStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();
    return randomString;
}

Использование потоков для реализации математических функций

Stream с, и особенно IntStream с, является элегантным способом реализации суммирования условий (a). Диапазоны Stream могут использоваться как границы суммирования.

Например, приближение Мадхавы к Pi дается формулой (Источник: wikipedia ): Мадхава

Это можно вычислить с произвольной точностью. Например, на 101 срок:

double pi = Math.sqrt(12) * 
            IntStream.rangeClosed(0, 100)
                     .mapToDouble(k -> Math.pow(-3, -1 * k) / (2 * k + 1))
                     .sum();

Примечание: с double точностью, выбирая верхнюю границу 29, достаточно, чтобы получить результат, который неотличим от Math.Pi

Использование потоков и ссылок на методы для записи самодокументирующих процессов

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

public interface Ordered {
    default int getOrder(){
        return 0;
    }
}

public interface Valued<V extends Ordered> {
    boolean hasPropertyTwo();
    V getValue();
}

public interface Thing<V extends Ordered> {
    boolean hasPropertyOne();
    Valued<V> getValuedProperty();
}

public <V extends Ordered> List<V> myMethod(List<Thing<V>> things) {
    List<V> results = new ArrayList<V>();
    for (Thing<V> thing : things) {
        if (thing.hasPropertyOne()) {
            Valued<V> valued = thing.getValuedProperty();
            if (valued != null && valued.hasPropertyTwo()){
                V value = valued.getValue();
                if (value != null){
                    results.add(value);
                }
            }
        }
    }
    results.sort((a, b)->{
        return Integer.compare(a.getOrder(), b.getOrder());
    });
    return results;
}

Этот последний метод, переписанный с использованием ссылок Stream и методов, намного читабельнее, и каждый шаг процесса быстро и легко понятен - он не просто короче, он также показывает с первого взгляда, какие интерфейсы и классы отвечают за код на каждом шаге:

public <V extends Ordered> List<V> myMethod(List<Thing<V>> things) {
    return things.stream()
        .filter(Thing::hasPropertyOne)
        .map(Thing::getValuedProperty)
        .filter(Objects::nonNull)
        .filter(Valued::hasPropertyTwo)
        .map(Valued::getValue)
        .filter(Objects::nonNull)
        .sorted(Comparator.comparing(Ordered::getOrder))
        .collect(Collectors.toList());
}    

Использование потоков Map.Entry для сохранения начальных значений после сопоставления

Когда у вас есть Stream вам нужно сопоставить, но вы хотите сохранить начальные значения, вы можете сопоставить Stream с Map.Entry<K,V> с помощью утилиты, например:

public static <K, V> Function<K, Map.Entry<K, V>> entryMapper(Function<K, V> mapper){
    return (k)->new AbstractMap.SimpleEntry<>(k, mapper.apply(k));
}

Затем вы можете использовать свой конвертер для обработки Stream имеющего доступ к исходным и отображаемым значениям:

Set<K> mySet;
Function<K, V> transformer = SomeClass::transformerMethod;
Stream<Map.Entry<K, V>> entryStream = mySet.stream()
    .map(entryMapper(transformer));

Затем вы можете продолжить обработку этого Stream как обычно. Это позволяет избежать накладных расходов на создание промежуточной коллекции.

Категории операций по потоку

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


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

Промежуточная операция всегда ленива , например простой Stream.map . Он не вызывается до тех пор, пока поток фактически не будет потреблен. Это легко проверить:

Arrays.asList(1, 2 ,3).stream().map(i -> {
    throw new RuntimeException("not gonna happen");
    return i;
});

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


Терминальные операции

Операции терминала - это то, что вызывает потребление потока. Некоторые из наиболее распространенных - Stream.forEach или Stream.collect . Они обычно размещаются после цепочки промежуточных операций и почти всегда стремятся .


Операции без гражданства

Безгражданство означает, что каждый элемент обрабатывается без контекста других элементов. Операции бездействия позволяют эффективно обрабатывать потоки с использованием памяти. Такие операции, как Stream.map и Stream.filter которые не требуют информации о других элементах потока, считаются апатридами.


Операции со штатом

Стойкость означает, что операция по каждому элементу зависит от (некоторых) других элементов потока. Для этого требуется сохранение состояния. Операции состояния могут прерываться с длинными или бесконечными потоками. Такие операции, как Stream.sorted требуют, чтобы весь поток обрабатывался до того, как Stream.sorted какой-либо элемент, который сломается в достаточно длинном потоке элементов. Это может быть продемонстрировано длинным потоком ( выполняется на свой страх и риск ):

// works - stateless stream
long BIG_ENOUGH_NUMBER = 999999999;
IntStream.iterate(0, i -> i + 1).limit(BIG_ENOUGH_NUMBER).forEach(System.out::println);

Это вызовет Stream.sorted памяти из-за Stream.sorted :

// Out of memory - stateful stream
IntStream.iterate(0, i -> i + 1).limit(BIG_ENOUGH_NUMBER).sorted().forEach(System.out::println);

Преобразование итератора в поток

Используйте Spliterators.spliterator() или Spliterators.spliteratorUnknownSize() для преобразования итератора в поток:

Iterator<String> iterator = Arrays.asList("A", "B", "C").iterator();    
Spliterator<String> spliterator = Spliterators.spliteratorUnknownSize(iterator, 0);
Stream<String> stream = StreamSupport.stream(spliterator, false);

Сокращение потока

Сокращение - это процесс применения бинарного оператора к каждому элементу потока, чтобы привести к одному значению.

Метод sum() для IntStream является примером сокращения; он применяет дополнение к каждому члену потока, что приводит к одному окончательному значению: Уменьшение суммы

Это эквивалентно (((1+2)+3)+4)

Метод reduce Stream позволяет создать индивидуальное сокращение. Для реализации метода sum() можно использовать метод reduce :

IntStream istr;
    
//Initialize istr
    
OptionalInt istr.reduce((a,b)->a+b);

Optional версия возвращается, так что пустые потоки могут обрабатываться соответствующим образом.

Другим примером сокращения является объединение Stream<LinkedList<T>> в один LinkedList<T> :

Stream<LinkedList<T>> listStream;
    
//Create a Stream<LinkedList<T>>
    
Optional<LinkedList<T>> bigList = listStream.reduce((LinkedList<T> list1, LinkedList<T> list2)->{
    LinkedList<T> retList = new LinkedList<T>();
    retList.addAll(list1);
    retList.addAll(list2);
    return retList;
});

Вы также можете предоставить элемент идентификации . Например, элемент идентификации для сложения равен 0, как x+0==x . Для умножения единичный элемент равен 1, так как x*1==x . В вышеприведенном случае элемент идентификации представляет собой пустой LinkedList<T> , потому что если вы добавляете пустой список в другой список, список, который вы добавляете, не изменяется:

Stream<LinkedList<T>> listStream;

//Create a Stream<LinkedList<T>>

LinkedList<T> bigList = listStream.reduce(new LinkedList<T>(), (LinkedList<T> list1, LinkedList<T> list2)->{
    LinkedList<T> retList = new LinkedList<T>();
    retList.addAll(list1);
    retList.addAll(list2);
    return retList;
});

Обратите внимание, что, когда предоставляется элемент идентификации, возвращаемое значение не обернуто в Optional -if, вызываемый в пустом потоке, reduce() вернет элемент идентификации.

Бинарный оператор также должен быть ассоциативным , что означает (a+b)+c==a+(b+c) . Это связано с тем, что элементы могут быть уменьшены в любом порядке. Например, приведенное выше сведение может быть выполнено следующим образом:

Сокращение других сумм

Это сокращение эквивалентно записи ((1+2)+(3+4)) . Свойство ассоциативности также позволяет Java сокращать Stream параллельно - часть потока может быть уменьшена каждым процессором, причем сокращение объединяет результат каждого процессора в конце.

Присоединение потока к одной строке

Часто используемый прецедент, создающий String из потока, где элементы потока разделяются определенным символом. Для этого можно использовать метод Collectors.joining() , как в следующем примере:

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");

String result = fruitStream.filter(s -> s.contains("a"))
           .map(String::toUpperCase)
           .sorted()
           .collect(Collectors.joining(", "));
           
System.out.println(result);

Выход:

APPLE, BANANA, ORANGE, PEAR

Метод Collectors.joining() может также обслуживать pre- и postfixes:

String result = fruitStream.filter(s -> s.contains("e"))
           .map(String::toUpperCase)
           .sorted()
           .collect(Collectors.joining(", ", "Fruits: ", "."));
           
System.out.println(result);

Выход:

Фрукты: APPLE, ORANGE, PEAR.

Живой на Ideone



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow