Потоки Java для начинающих: введение в использование потоков в Java

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

До выпуска Java 8 использование термина «поток» в Java автоматически ассоциировалось с вводом-выводом. Однако в Java 8 появился поток, который можно назвать набором вычислительных шагов, связанных вместе в так называемый «конвейер потока».

Эта статья познакомит вас с потоками Java 8 и продемонстрирует, как они могут быть полезны в ваших проектах.

Что такое поток?

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

Как работают потоки

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

Чтобы преобразовать коллекцию в исходный поток, вам нужно добавить к источнику функцию stream () . Это поместит источник в конвейер потока, где с ним могут работать несколько различных промежуточных операций (таких как filter () и sort () ).

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

Жизнь без потоков

Java 8 была выпущена в 2014 году, но до этого разработчикам Java все еще требовалось извлекать специализированные данные из набора общих данных.

Допустим, у вас есть список случайных символов, которые комбинируются со случайными числами для формирования уникальных строковых значений, но вам нужны только значения, начинающиеся с символа «C», и вы хотите расположить результат в порядке возрастания. Вот как вы извлекаете эти данные без потоков.

Связано: что вам нужно знать об использовании строк в Java

Пример фильтрации и сортировки значений без потоков

 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
//declare and initialize the array list
List<String> randomValues = Arrays.asList(
"E11", "D12", "A13", "F14", "C15", "A16",
"B11", "B12", "C13", "B14", "B15", "B16",
"F12", "E13", "C11", "C14", "A15", "C16",
"F11", "C12", "D13", "E14", "D15", "D16"
);
//declare the array list will store needed values
List<String> requiredValues = new ArrayList<>();
//extracting the required values and storing them in reqquiredValues
randomValues.forEach(value -> {
if(value.startsWith("C")) {
requiredValues.add(value);
}
});
//sort the requiredValues in ascending order
requiredValues.sort((String value1, String value2) -> value1.compareTo(value2));
//print each value to the console
requiredValues.forEach((String value) -> System.out.println(value));
}
}

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

Связанный: Как создавать и выполнять операции с массивами в Java

Приведенный выше код дает в консоли следующий вывод:

 
C11
C12
C13
C14
C15
C16

Жизнь с потоками

В программировании эффективность означает получение того же результата при значительно меньшем количестве кода. Именно это и делает для программиста потоковый конвейер. Поэтому в следующий раз, когда кто-то спросит: «Почему важно использовать потоки в вашем проекте?» Проще говоря: «потоки поддерживают эффективное программирование».

Продолжая наш пример выше, вот как введение потоков трансформирует всю программу.

Фильтрация и сортировка значений на примере потока

 
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
//declare and initialize the array list
List<String> randomValues = Arrays.asList(
"E11", "D12", "A13", "F14", "C15", "A16",
"B11", "B12", "C13", "B14", "B15", "B16",
"F12", "E13", "C11", "C14", "A15", "C16",
"F11", "C12", "D13", "E14", "D15", "D16"
);
//retrieves only values that start with C, sort them, and print them to the console.
randomValues.stream().filter(value->value.startsWith("C")).sorted().forEach(System.out::println);
}
}

Приведенный выше код демонстрирует, насколько мощным является потоковый интерфейс. Он принимает список случайных значений массива и преобразует его в поток с помощью функции stream () . Затем поток сводится к списку массивов, который содержит необходимые значения (это все значения, начинающиеся с C ) с помощью функции filter () .

Как вы можете видеть в приведенном выше примере, значения C расположены в списке массивов случайным образом. Если бы вы распечатали поток на этом этапе конвейера, сначала было бы напечатано значение C15 . Поэтому в конвейер потока вводится функция sort (), которая переупорядочивает новый массив в порядке возрастания.

Последняя функция в конвейере потока – это функция forEach () . Это функция терминала, которая используется для остановки конвейера потока и дает следующие результаты в консоли:

 
C11
C12
C13
C14
C15
C16

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

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

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

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

Если бы какое-либо из значений, начинающихся с «C» в нашем списке массивов выше, было в нижнем регистре, и мы выполнили бы те же промежуточные операции с ними, мы бы получили бы следующий результат.

Пример выполнения операций фильтрации и сортировки для значений в нижнем регистре

 
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
//declare and initialize the array list
List<String> randomValues = Arrays.asList(
"E11", "D12", "A13", "F14", "C15", "A16",
"B11", "B12", "c13", "B14", "B15", "B16",
"F12", "E13", "C11", "C14", "A15", "c16",
"F11", "C12", "D13", "E14", "D15", "D16"
);
//retrieves only values that start with C, sort them, and print them to the console.
randomValues.stream().filter(value->value.startsWith("C")).sorted().forEach(System.out::println);
}
}

Приведенный выше код выдаст в консоли следующие значения:

 
C11
C12
C14
C15

Единственная проблема с выходными данными выше заключается в том, что они не точно представляют все значения C в нашем списке массивов. Хороший способ исправить эту небольшую ошибку – ввести в конвейер потока еще одну промежуточную операцию; эта операция известна как функция map () .

Использование примера функции карты

 
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
//declare and initialize the array list
List<String> randomValues = Arrays.asList(
"E11", "D12", "A13", "F14", "C15", "A16",
"B11", "B12", "c13", "B14", "B15", "B16",
"F12", "E13", "C11", "C14", "A15", "c16",
"F11", "C12", "D13", "E14", "D15", "D16"
);
//transforms all lower case characters to upper case,
//retrieves only values that start with C, sort them, and print them to the console.
randomValues.stream().map(String::toUpperCase).filter(value->value.startsWith("C")).sorted().forEach(System.out::println);
}
}

Функция map () переводит объект из одного состояния в другое; в нашем примере выше он преобразует все символы нижнего регистра в списке массивов в символы верхнего регистра.

Размещение функции map () непосредственно перед функцией filter () извлекает все значения, начинающиеся с C, из списка массивов.

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

 
C11
C12
C13
C14
C15
C16

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

  • заглянуть ()
  • предел ()
  • пропускать()

Потоки Java 8 упрощают создание эффективного кода

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

Если вам интересно, какая строка кода заключена в нашу функцию filter () ; это известно как «лямбда-выражение». Лямбда-выражения – еще одна функция, представленная в Java 8, и в ней есть много самородков, которые могут оказаться полезными.