Статьи

Станьте мастером потоков Java. Часть 2. Промежуточные операции

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

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

  • Часть 1. Создание потоков
  • Часть 2: Промежуточные операции
  • Часть 3: Терминальные операции
  • Часть 4. Потоки базы данных
  • Часть 5: Создание приложения базы данных с использованием потоков

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

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

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

(*) Строго говоря, промежуточная операция не обязана создавать новый поток. Вместо этого он может обновить свое внутреннее состояние или, если промежуточная операция ничего не изменила (например, .skip(0) ), вернуть существующий поток из предыдущего этапа.

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

1
2
3
4
5
6
7
List<String> list = Stream.of("Monkey", "Lion", "Giraffe","Lemur")
    .filter(s -> s.startsWith("L"))
    .map(String::toUpperCase)
    .sorted()
    .collect(toList());
 
System.out.println(list);
1
[LEMUR, LION]

Теперь мы продолжим объяснять значение этих и других операций более подробно.

Фильтр

Исходя из нашего опыта, filter() является одной из самых полезных операций Stream API. Это позволяет сузить поток до элементов, которые соответствуют определенным критериям. Такие критерии должны быть выражены как Predicate (функция, приводящая к boolean значению), например, лямбда. Цель приведенного ниже кода — найти строки, начинающиеся с буквы «L», и отбросить остальные.

1
2
3
4
5
Stream<String> startsWithT = Stream.of(
   "Monkey", "Lion", "Giraffe", "Lemur"
)
 
    .filter(s -> s.startsWith("L"));
1
startsWithT: [Lion, Lemur]

предел

Существуют очень простые, но мощные операции, которые позволяют выбирать или отбрасывать элементы в зависимости от их положения в потоке. Первой из этих операций является limit(n) который в основном делает то, что говорит — он создает новый поток, который содержит только первые n элементов потока, к которому он применяется. Приведенный ниже пример иллюстрирует, как поток из четырех животных сокращен до «Обезьяны» и «Льва».

1
2
3
4
Stream<String> firstTwo = Stream.of(
   "Monkey", "Lion", "Giraffe", "Lemur"
)
   .limit(2);
1
firstTwo: [Monkey, Lion]


Пропускать

Точно так же, если мы заинтересованы только в некоторых элементах, мы можем использовать .skip(n) . Если мы применим skip(2) к нашему Потоку животных, у нас останется два элемента «Жираф» и «Лемур».

1
2
3
4
Stream<String> firstTwo = Stream.of(
   "Monkey", "Lion", "Giraffe", "Lemur"
)
   .skip(2);
1
lastTwo: [Giraffe, Lemur]

отчетливый

Существуют также ситуации, когда нам нужно только одно вхождение каждого элемента потока. Вместо того, чтобы отфильтровывать дубликаты вручную, для этой цели существует назначенная операция — distinct() . Он проверит на равенство, используя Object::equals и вернет новый поток только с уникальными элементами. Это похоже на набор.

1
2
3
4
Stream<String> uniqueAnimals = Stream.of(
   "Monkey", "Lion", "Giraffe", "Lemur", "Lion"
)
   .distinct();
1
uniqueAnimals: [“Monkey”, “Lion”, “Giraffe”, “Lemur”]


Сортировка

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

1
2
3
4
Stream<String> alphabeticOrder = Stream.of(
    "Monkey", "Lion", "Giraffe", "Lemur"
)
   .sorted();
1
alphabeticOrder: [Giraffe, Lemur, Lion, Monkey]


Сортировка с компаратором

Наличие возможности сортировки в естественном порядке иногда может быть немного ограничивающим. К счастью, можно применить собственный Comparator для проверки определенного свойства элемента. Мы могли бы, например, упорядочить строки после их длины соответственно:

1
2
3
4
Stream<String> lengthOrder = Stream.of(
    "Monkey", "Lion", "Giraffe", "Lemur"
)
   .sorted(Comparator.comparing(String::length));
1
lengthOrder: [Lion, Lemur, Monkey, Giraffe]

карта

Одной из самых универсальных операций, которые мы можем применить к потоку, является map() . Он позволяет преобразовывать элементы потока во что-то другое, сопоставляя их с другим значением или типом. Это означает, что результатом этой операции может быть поток любого типа R В приведенном ниже примере выполняется простое преобразование из String в String , заменяя заглавные буквы на строчные.

1
2
3
4
Stream<String> lowerCase = Stream.of(
    "Monkey", "Lion", "Giraffe", "Lemur"
)
   .map(String::toLowerCase);
1
lowerCase: [monkey, lion, giraffe, lemur]


Карта на целое, двойное или длинное

Есть также три специальные реализации операции map, которые ограничены отображением элементов в примитивные типы int , double и
long

1
2
3
.mapToInt();
.mapToDouble();
.mapToLong();

Следовательно, результат этих операций всегда соответствует IntStream , LongStream или LongStream . Ниже мы продемонстрируем, как .mapToInt() можно использовать для сопоставления наших животных с длиной их имен:

1
2
3
4
IntStream lengths = Stream.of(
    "Monkey", "Lion", "Giraffe", "Lemur"
)
   .mapToInt(String::length);
1
lengths: [6, 4, 7, 5]

Замечания:
String::length является эквивалентом лямбды s -> s.length() . Мы предпочитаем первую нотацию, так как она делает код более лаконичным и читабельным.


FlatMap

Последняя операция, о которой мы расскажем в этой статье, может оказаться более сложной для понимания, хотя она может быть довольно мощной. Это связано с операцией map() но вместо того, чтобы брать Function которая переходит от типа T к типу возврата R , она берет Function которая переходит от типа T и возвращает Stream R Эти «внутренние» потоки затем сглаживаются в результирующие потоки, что приводит к объединению всех элементов внутренних потоков.

1
2
3
4
Stream<Character> chars = Stream.of(
    "Monkey", "Lion", "Giraffe", "Lemur"
)
    .flatMap(s -> s.chars().mapToObj(i -> (char) i));
1
chars: [M, o, n, k, e, y, L, i, o, n, G, i, r, a, f, f, e, L, e, m, u, r]


упражнения

Если вы еще не клонировали связанный репозиторий GitHub, мы рекомендуем вам сделать это сейчас. Содержание этой статьи достаточно, чтобы решить второй блок, который называется MyUnit2Intermediate . Соответствующий интерфейс Unit2Intermediate содержит JavaDocs, который описывает предполагаемую реализацию методов в MyUnit2MyIntermediate .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public interface Unit2Intermediate {
   /**
    * Return a Stream that contains words that are
    * longer than three characters. Shorter words
    * (i.e. words of length 0, 1, 2 and 3)
    * shall be filtered away from the stream.
    * <p>
    *  A Stream of
    *      ["The", "quick", "quick", "brown", "fox",
    *      "jumps", "over", "the", "lazy", "dog"]
    *  would produce a Stream of the elements
    *      ["quick", "quick", "brown", "jumps",
    *      "over", "lazy"]
    */
 
   Stream<String> wordsLongerThanThreeChars(Stream<String> stream);

Предоставленные тесты (например, Unit2MyIntermediateTest ) будут действовать как инструмент автоматической оценки, сообщая вам, было ли ваше решение правильным или нет.

Следующая статья

В следующей статье мы переходим к терминальным операциям и исследуем, как мы можем собирать, подсчитывать или группировать полученные элементы нашего конвейера. До тех пор — счастливого кодирования!

Авторы

Пер Минборг и Юлия Густафссон

См. Оригинальную статью здесь: стать мастером потоков Java — часть 2: промежуточные операции

Мнения, высказанные участниками Java Code Geeks, являются их собственными.