Java Streams: A Beginner's Guide

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

Java - Streams

Что такое Stream в Java?

Представьте, что вы на conveyer belt sushi restaurant. Листы суши continuously движутся мимо вас, и вы можете выбирать, что вам нравится. Это Essentially то, что такое Stream в Java – этоsequence элементов, которые вы можете обрабатывать один за другим, не сохраняя их все в памяти сразу.

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

Создание Streams в Java

Давайте начнем с создания нашего первого Stream. Есть несколько способов сделать это, но мы начнем с простого примера:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {
public static void main(String[] args) {
// Создание Stream из List
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
Stream<String> fruitStream = fruits.stream();

// Создание Stream напрямую
Stream<Integer> numberStream = Stream.of(1, 2, 3, 4, 5);
}
}

В этом примере мы создали два Streams. Первый, fruitStream, создан из List фруктов. Второй, numberStream, создан напрямую с помощью метода Stream.of().

Общие операции с Streams

Теперь, когда у нас есть наши Streams, давайте посмотрим на некоторые общие операции, которые мы можем выполнять с ними.

Метод forEach

Метод forEach похож на дружелюбного робота, который проходит через каждый элемент в вашем Stream и что-то с ним делает. Давайте kullanیم его для打印 наших фруктов:

fruits.stream().forEach(fruit -> System.out.println(fruit));

Это напечатает:

apple
banana
cherry
date

Здесь fruit -> System.out.println(fruit) называется лямбда-выражением. Это короткий способ сказать Java, что делать с каждым элементом.

Метод map

Метод map похож на магическую палочку, которая transformирует каждый элемент в Stream. Давайте используем его, чтобы сделать все наши фрукты прописными:

List<String> upperCaseFruits = fruits.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(upperCaseFruits);

Это напечатает:

[APPLE, BANANA, CHERRY, DATE]

String::toUpperCase – это метод referrals, еще одна полезная функция Java, которая говорит "используйте метод toUpperCase на каждой строке".

Метод filter

Метод filter похож на охранника в俱乐部е, который пропускает только определенные элементы. Давайте используем его, чтобы оставить только фрукты, начинающиеся с 'a':

List<String> aFruits = fruits.stream()
.filter(fruit -> fruit.startsWith("a"))
.collect(Collectors.toList());
System.out.println(aFruits);

Это напечатает:

[apple]

Метод limit

Метод limit похож на "Я хочу только столько, спасибо!" Он ограничивает Stream определенным количеством элементов:

List<String> firstTwoFruits = fruits.stream()
.limit(2)
.collect(Collectors.toList());
System.out.println(firstTwoFruits);

Это напечатает:

[apple, banana]

Метод sorted

Метод sorted похож на arrangement вашей книжной полки. Он puts элементы в порядке:

List<String> sortedFruits = fruits.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedFruits);

Это напечатает:

[apple, banana, cherry, date]

Параллельная обработка

Одна из cool вещей о Streams заключается в том, что их можно легко обрабатывать параллельно, потенциально ускоряя операции на больших наборах данных. Вы можете создать параллельный Stream следующим образом:

fruits.parallelStream().forEach(fruit -> System.out.println(fruit + " " + Thread.currentThread().getName()));

Это может напечатать что-то вроде:

banana ForkJoinPool.commonPool-worker-1
apple main
date ForkJoinPool.commonPool-worker-2
cherry ForkJoinPool.commonPool-worker-3

Порядок может быть khác nhau каждый раз, когда вы запускаете его, потому что фрукты обрабатываются параллельно!

Collectors

Collectors – это special инструменты, которые помогают нам собирать результаты операций Stream. Мы использовали collect(Collectors.toList()) в наших примерах, чтобы вернуть наши результаты Stream обратно в List. Есть много других полезных Collectors:

// Объединение элементов в строку
String fruitString = fruits.stream().collect(Collectors.joining(", "));
System.out.println(fruitString);  // Печатает: apple, banana, cherry, date

// Подсчет элементов
long fruitCount = fruits.stream().collect(Collectors.counting());
System.out.println(fruitCount);  // Печатает: 4

// Группировка элементов
Map<Character, List<String>> fruitGroups = fruits.stream()
.collect(Collectors.groupingBy(fruit -> fruit.charAt(0)));
System.out.println(fruitGroups);  // Печатает: {a=[apple], b=[banana], c=[cherry], d=[date]}

Статистика

Streams также могут помочь нам calculate статистику на числовых данных:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt(Integer::intValue).summaryStatistics();
System.out.println("Count: " + stats.getCount());
System.out.println("Sum: " + stats.getSum());
System.out.println("Min: " + stats.getMin());
System.out.println("Max: " + stats.getMax());
System.out.println("Average: " + stats.getAverage());

Это напечатает:

Count: 5
Sum: 15
Min: 1
Max: 5
Average: 3.0

Пример Java Streams

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

class Student {
String name;
int age;
double grade;

Student(String name, int age, double grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
}

public class StreamExample {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("Alice", 22, 90.5),
new Student("Bob", 20, 85.0),
new Student("Charlie", 21, 92.3),
new Student("David", 23, 88.7)
);

// Получение средней оценки студентов старше 21
double averageGrade = students.stream()
.filter(student -> student.age > 21)
.mapToDouble(student -> student.grade)
.average()
.orElse(0.0);

System.out.println("Средняя оценка студентов старше 21: " + averageGrade);

// Получение имен двух лучших студентов по оценке
List<String> topStudents = students.stream()
.sorted((s1, s2) -> Double.compare(s2.grade, s1.grade))
.limit(2)
.map(student -> student.name)
.collect(Collectors.toList());

System.out.println("Два лучших студента: " + topStudents);
}
}

Этот пример demonstrates, как мы можем связывать multiple операции Stream для выполнения сложных задач обработки данных в краткой и читаемой форме.

И вот вы и здесь, folks! Вы только что сделали свои первые шаги в мир Java Streams. Помните, что практика делает мастера, так что не бойтесь experimenting с этими концепциями. Before you know it, вы будете обрабатывать данные, как профессионал! Happy coding!

Credits: Image by storyset