Статьи

Java 8 Streams: введение в фильтрацию, сопоставление и сокращение операций

Узнайте больше о Java с нашим учебником Соединение Android и Java в разработке Android на SitePoint.

Уклонившись от них в течение многих лет, Java наконец приняла конструктивные функциональные программные конструкции весной 2014 года. Java 8 включает поддержку лямбда-выражений и предлагает мощный API-интерфейс Streams, который позволяет работать с последовательностями элементов, такими как списки и массивы, совершенно новым способом.

В этом уроке я собираюсь показать вам, как создавать потоки, а затем преобразовывать их с помощью трех широко используемых методов высшего порядка, называемых map , filter и reduce .

Код для этого поста можно найти здесь .

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

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

В Java 8 каждый класс, который реализует интерфейс java.util.Collection имеет метод stream который позволяет вам преобразовывать его экземпляры в объекты Stream . Следовательно, любой список легко преобразовать в поток. Вот пример, который преобразует ArrayList объектов Integer в Stream :

 // Create an ArrayList List<Integer> myList = new ArrayList<Integer>(); myList.add(1); myList.add(5); myList.add(8); // Convert it into a Stream Stream<Integer> myStream = myList.stream(); 

Если вы предпочитаете массивы спискам, вы можете использовать метод stream доступный в классе Arrays для преобразования любого массива в поток. Вот еще один пример:

 // Create an array Integer[] myArray = {1, 5, 8}; // Convert it into a Stream Stream<Integer> myStream = Arrays.stream(myArray); 

ручей

Метод map

Получив объект Stream , вы можете использовать различные методы для его преобразования в другой объект Stream . Первый такой метод, который мы рассмотрим, это метод map . Он принимает лямбда-выражение в качестве единственного аргумента и использует его для изменения каждого отдельного элемента в потоке. Его возвращаемое значение — новый объект Stream содержащий измененные элементы.

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

Вы начинаете с преобразования массива в Stream :

 String[] myArray = new String[]{"bob", "alice", "paul", "ellie"}; Stream<String> myStream = Arrays.stream(myArray); 

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

 Stream<String> myNewStream = myStream.map(s -> s.toUpperCase()); 

Stream объект Stream содержит измененные строки. Чтобы преобразовать его в массив, вы используете метод toArray :

 String[] myNewArray = myNewStream.toArray(String[]::new); 

На данный момент у вас есть массив строк, все из которых в верхнем регистре.

Я надеюсь, что вы сейчас начинаете понимать, что с этим стилем программирования вы можете покончить с циклами, и код, который вы пишете, может быть очень кратким и читаемым.

Метод filter

В предыдущем разделе вы видели, что метод map обрабатывает каждый элемент в объекте Stream . Возможно, вы не всегда этого хотите. Иногда вам может понадобиться работать только с подмножеством элементов. Для этого вы можете использовать метод filter .

Как и метод map метод filter ожидает лямбда-выражение в качестве аргумента. Однако переданное ему лямбда-выражение всегда должно возвращать boolean значение, которое определяет, должен ли обработанный элемент принадлежать результирующему объекту Stream .

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

 Arrays.stream(myArray) .filter(s -> s.length() > 4) .toArray(String[]::new); 

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

Операции сокращения

Операция сокращения — это операция, которая позволяет вычислить результат, используя все элементы, присутствующие в потоке. Операции сокращения также называются терминальными операциями, поскольку они всегда присутствуют в конце цепочки методов Stream . Мы уже использовали метод сокращения в наших предыдущих примерах: метод toArray . Это терминальная операция, потому что она преобразует объект Stream в массив.

Java 8 включает несколько методов сокращения, таких как sum , average и count , которые позволяют выполнять арифметические операции над объектами Stream и получать числа в качестве результатов. Например, если вы хотите найти сумму массива целых чисел, вы можете использовать следующий код:

 int myArray[] = { 1, 5, 8 }; int sum = Arrays.stream(myArray).sum(); 

Однако, если вы хотите выполнить более сложные операции сокращения, вы должны использовать метод reduce . В отличие от методов map и filter , метод Reduce предполагает два аргумента: элемент тождества и лямбда-выражение. Вы можете рассматривать элемент идентичности как элемент, который не изменяет результат операции сокращения. Например, если вы пытаетесь найти произведение всех элементов в потоке чисел, элементом идентичности будет номер 1.

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

Ниже приведен пример фрагмента кода, который использует метод Reduce для объединения всех элементов в массиве объектов String :

 String[] myArray = { "this", "is", "a", "sentence" }; String result = Arrays.stream(myArray) .reduce("", (a,b) -> a + b); 

Вывод

Теперь вы знаете достаточно, чтобы начать использовать map , filter и reduce методы в своих проектах. Для краткости в этом уроке я использовал только последовательные потоки. Если у вас много вычислительных операций с картами, или если вы ожидаете, что ваши потоки будут очень большими, вам следует рассмотреть возможность использования параллельных потоков. К счастью, очень легко преобразовать любой поток в его параллельный эквивалент. Все, что вам нужно сделать, это вызвать его parallel метод.

Я также хотел бы сообщить вам, что если вы предпочитаете не использовать лямбда-выражения при работе с методами map , filter и reduce , вы всегда можете вместо этого использовать ссылки на методы.

Чтобы узнать больше о Streams API и других методах, которые он может предложить, вы можете обратиться к его документации .

Узнайте больше о Java с нашим учебником Соединение Android и Java в разработке Android на SitePoint.