Потоки Java для начинающих: введение в использование потоков в Java
Потоки Java 8 позволяют разработчикам извлекать точные данные из большой коллекции, используя набор предопределенных операций.
До выпуска Java 8 использование термина «поток» в Java автоматически ассоциировалось с вводом-выводом. Однако в Java 8 появился поток, который можно назвать набором вычислительных шагов, связанных вместе в так называемый «конвейер потока».
Эта статья познакомит вас с потоками Java 8 и продемонстрирует, как они могут быть полезны в ваших проектах.
Что такое поток?
Поток – это интерфейс Java, который принимает источник, выполняет набор операций для извлечения определенных данных, а затем предоставляет эти данные приложению для использования. По сути, он позволяет извлекать специализированные данные из набора обобщенных данных.
Как работают потоки
Потоковый конвейер всегда начинается с источника. Тип источника зависит от типа данных, с которыми вы имеете дело, но двумя наиболее популярными из них являются массивы и коллекции.
Чтобы преобразовать коллекцию в исходный поток, вам нужно добавить к источнику функцию stream () . Это поместит источник в конвейер потока, где с ним могут работать несколько различных промежуточных операций (таких как filter () и sort () ).
После выполнения всех необходимых промежуточных операций вы можете ввести терминальную операцию (например, forEach () ), которая будет производить ранее извлеченные данные из источника.
Жизнь без потоков
Java 8 была выпущена в 2014 году, но до этого разработчикам Java все еще требовалось извлекать специализированные данные из набора общих данных.
Допустим, у вас есть список случайных символов, которые комбинируются со случайными числами для формирования уникальных строковых значений, но вам нужны только значения, начинающиеся с символа «C», и вы хотите расположить результат в порядке возрастания. Вот как вы извлекаете эти данные без потоков.
Пример фильтрации и сортировки значений без потоков
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));
}
}
Вам также необходимо объявить и инициализировать список массивов, независимо от того, используете ли вы потоки или какой-либо другой метод извлечения. Что вам не нужно делать, если вы используете потоки, так это объявлять новую переменную для хранения требуемых значений или создавать другие пять с лишним строк кода в приведенном выше примере.
Приведенный выше код дает в консоли следующий вывод:
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, и в ней есть много самородков, которые могут оказаться полезными.