Java Stream API: Lambda, Functional Interfaces, Map, Filter, Reduce & Collect
Эта статья содержит полное руководство по Java Stream API с рассмотрением Lambda-выражений, Functional Interfaces, операций map, filter, reduce и collect на практических примерах.
В двух словах
Java Stream API позволяет функционально обрабатывать данные с помощью Lambda-выражений. map преобразует элементы, filter выбирает нужные, reduce агрегирует результаты, а collect собирает их в контейнеры.
Определение
Java Stream API – это функциональный API для обработки коллекций данных. Он поддерживает декларативное программирование с использованием Lambda-выражений и Functional Interfaces.
Ключевые концепции:
Lambda-выражения
- Синтаксис:
(parameter) -> expressionили(parameter) -> { statements } - Вывод типов: тип определяется из контекста
- Effectively final: переменные должны быть final или effectively final
- Ссылки на методы: более краткая запись для Lambda-выражений
Functional Interfaces
- **Predicate<T>**: boolean test(T t) - проверка
- **Function<T,R>**: R apply(T t) - преобразование
- **Consumer<T>**: void accept(T t) - потребление
- **Supplier<T>**: T get() - создание
- **UnaryOperator<T>**: T apply(T t) - унарная операция
- **BinaryOperator<T>**: T apply(T t1, T t2) - бинарная операция
```java
### Stream-операции
- **Intermediate**: map, filter, sorted, distinct, limit, skip
- **Terminal**: forEach, collect, reduce, count, anyMatch, allMatch
- **Short-circuiting**: findFirst, findAny, anyMatch, allMatch, noneMatch
## Ключевые моменты
- **Stream API**: функциональная обработка данных в Java 8+
- **Lambda-выражения**: анонимные функции с компактным синтаксисом
- **Functional Interfaces**: интерфейсы с одним абстрактным методом
- **Map**: преобразование элементов
- **Filter**: выборка на основе предикатов
- **Reduce**: агрегация элементов потока
- **Collect**: сбор результатов в контейнеры
- **Актуально для экзаменов**: современный Java, функциональное программирование
## Основные компоненты
1. **Lambda-выражения**: компактные функциональные литералы
2. **Functional Interfaces**: типизированные функциональные определения
3. **Создание потоков**: из коллекций, массивов, I/O, генераторов
4. **Intermediate Operations**: ленивые, цепочечные операции
5. **Terminal Operations**: энергичные операции, завершающие обработку
6. **Collectors**: специализированные операции сбора
7. **Parallel Streams**: параллельная обработка данных
8. **Optional**: контейнер, безопасный от null-значений
## Практические примеры
### 1. Основные операции потоков
```java
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class StreamGrundlagen {
public static void main(String[] args) {
System.out.println("=== Stream API Grundlagen ===");
// Datenquelle
List<String> namen = Arrays.asList("Alice", "Bob", "Charlie", "Diana", "Eve");
List<Integer> zahlen = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Filter Demo
filterDemo(namen, zahlen);
// Map Demo
mapDemo(namen, zahlen);
// Reduce Demo
reduceDemo(zahlen);
// Collect Demo
collectDemo(namen, zahlen);
// Методенреференцы
methodenreferenzenDemo();
}
private static void filterDemo(List<String> namen, List<Integer> zahlen) {
System.out.println("\n--- Filter Demo ---");
// Lambda-выражение для фильтра
List<String> langeNamen = namen.stream()
.filter(name -> name.length() > 4)
.collect(Collectors.toList());
System.out.println("Namen mit > 4 Buchstaben: " + langeNamen);
// Несколько фильтров
List<Integer> gefilterteZahlen = zahlen.stream()
.filter(zahl -> zahl % 2 == 0) // Gerade Zahlen
.filter(zahl -> zahl > 3) // Größer als 3
.collect(Collectors.toList());
System.out.println("Gerade Zahlen > 3: " + gefilterteZahlen);
// Сложный предикат
Predicate<String> komplexesPraedikat = name ->
name.startsWith("A") && name.length() <= 5;
List<String> gefilterteNamen = namen.stream()
.filter(komplexesPraedikat)
.collect(Collectors.toList());
System.out.println("Namen mit 'A' und ≤5 Buchstaben: " + gefilterteNamen);
}
private static void mapDemo(List<String> namen, List<Integer> zahlen) {
System.out.println("\n--- Map Demo ---");
// String в Integer (длина)
List<Integer> namenslaengen = namen.stream()
.map(name -> name.length())
.collect(Collectors.toList());
System.out.println("Namenslängen: " + namenslaengen);
// Integer в String (квадраты)
List<String> quadrate = zahlen.stream()
.map(zahl -> zahl * zahl)
.map(quad -> "Quadrat: " + quad)
.collect(Collectors.toList());
System.out.println("Quadrate: " + quadrate);
// FlatMap для вложенных структур
List<List<Integer>> verschachtelt = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5),
Arrays.asList(6, 7, 8, 9)
);
List<Integer> flach = verschachtelt.stream()
.flatMap(list -> list.stream())
.collect(Collectors.toList());
System.out.println("Flachgemacht: " + flach);
// Map с объектами
List<Person> personen = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 35)
);
List<String> personenInfo = personen.stream()
.map(person -> person.getName() + " (" + person.getAlter() + ")")
.collect(Collectors.toList());
System.out.println("Personen-Info: " + personenInfo);
}
private static void reduceDemo(List<Integer> zahlen) {
System.out.println("\n--- Reduce Demo ---");
// Сумма с помощью reduce
Optional<Integer> summe = zahlen.stream()
.reduce((a, b) -> a + b);
System.out.println("Summe: " + summe.orElse(0));
// Произведение с помощью reduce
Optional<Integer> produkt = zahlen.stream()
.reduce((a, b) -> a * b);
System.out.println("Produkt: " + produkt.orElse(1));
// Максимум с помощью reduce
Optional<Integer> maximum = zahlen.stream()
.reduce(Integer::max);
System.out.println("Maximum: " + maximum.orElse(0));
// Reduce с начальным значением
int summeMitIdentitaet = zahlen.stream()
.reduce(0, Integer::sum);
System.out.println("Summe mit Identität: " + summeMitIdentitaet);
// Конкатенация строк
List<String> woerter = Arrays.asList("Java", "Stream", "API");
Optional<String> verkettet = woerter.stream()
.reduce((a, b) -> a + " " + b);
System.out.println("Verkettet: " + verkettet.orElse(""));
}
private static void collectDemo(List<String> namen, List<Integer> zahlen) {
System.out.println("\n--- Collect Demo ---");
// To List
List<String> grossgeschrieben = namen.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Großgeschrieben: " + grossgeschrieben);
// To Set
Set<Integer> quadrate = zahlen.stream()
.map(zahl -> zahl * zahl)
.collect(Collectors.toSet());
System.out.println("Quadrate als Set: " + quadrate);
// To Map
Map<String, Integer> namenMap = namen.stream()
.collect(Collectors.toMap(
name -> name, // Key-Mapper
name -> name.length() // Value-Mapper
));
System.out.println("Namen-Map: " + namenMap);
// Grouping By
Map<Integer, List<String>> nachLaengeGruppiert = namen.stream()
.collect(Collectors.groupingBy(String::length));
System.out.println("Nach Länge gruppiert: " + nachLaengeGruppiert);
// Partitioning By
Map<Boolean, List<Integer>> geradeUngerade = zahlen.stream()
.collect(Collectors.partitioningBy(zahl -> zahl % 2 == 0));
System.out.println("Partitioniert: " + geradeUngerade);
// Joining
String namensliste = namen.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println("Namensliste: " + namensliste);
// Summarizing
IntSummaryStatistics statistik = zahlen.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
System.out.println("Statistik: " + statistik);
}
private static void methodenreferenzenDemo() {
System.out.println("\n--- Methodenreferenzen Demo ---");
List<String> namen = Arrays.asList("alice", "bob", "charlie");
// Ссылка на статический метод
List<String> grossgeschrieben = namen.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Statische Referenz: " + grossgeschrieben);
// Ссылка на метод экземпляра
List<Integer> laengen = namen.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println("Instanz-Referenz: " + laengen);
// Ссылка на конструктор
List<Person> personen = namen.stream()
.map(name -> new Person(name, 20 + name.length()))
.collect(Collectors.toList());
System.out.println("Konstruktor-Referenz: " +
personen.stream()
.map(Person::getName)
.collect(Collectors.toList()));
}
// Hilfsklasse
static class Person {
private String name;
private int alter;
public Person(String name, int alter) {
this.name = name;
this.alter = alter;
}
public String getName() { return name; }
public int getAlter() { return alter; }
}
}
2. Продвинутые операции со Stream
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class FortgeschritteneStreams {
public static void main(String[] args) {
System.out.println("=== Fortgeschrittene Stream Operationen ===");
// Daten für Demonstrationen
List<Student> studenten = Arrays.asList(
new Student("Alice", "Informatik", 85, 3),
new Student("Bob", "Mathematik", 92, 2),
new Student("Charlie", "Informatik", 78, 4),
new Student("Diana", "Physik", 88, 1),
new Student("Eve", "Informatik", 95, 2),
new Student("Frank", "Mathematik", 73, 3)
);
// Sortierung
sortierungDemo(studenten);
// Limit und Skip
limitSkipDemo(studenten);
// Distinct
distinctDemo();
// Match-Operationen
matchDemo(studenten);
// Find-Operationen
findDemo(studenten);
// Optional-Handling
optionalDemo(studenten);
// Parallel Streams
parallelStreamDemo(studenten);
}
private static void sortierungDemo(List<Student> studenten) {
System.out.println("\n--- Sortierung Demo ---");
// Nach Note sortieren
List<Student> nachNote = studenten.stream()
.sorted(Comparator.comparing(Student::getNote))
.collect(Collectors.toList());
System.out.println("Nach Note aufsteigend:");
nachNote.forEach(s -> System.out.println(" " + s.getName() + ": " + s.getNote()));
// Nach Note absteigend
List<Student> nachNoteAbsteigend = studenten.stream()
.sorted(Comparator.comparing(Student::getNote).reversed())
.collect(Collectors.toList());
System.out.println("\nNach Note absteigend:");
nachNoteAbsteigend.forEach(s -> System.out.println(" " + s.getName() + ": " + s.getNote()));
// Mehrkriteriensortierung
List<Student> mehrkriterium = studenten.stream()
.sorted(Comparator
.comparing(Student::getFach)
.thenComparing(Student::getNote)
.thenComparing(Student::getName))
.collect(Collectors.toList());
System.out.println("\nNach Fach, Note, Name:");
mehrkriterium.forEach(s -> System.out.println(" " + s.getFach() + " - " +
s.getName() + ": " + s.getNote()));
}
private static void limitSkipDemo(List<Student> studenten) {
System.out.println("\n--- Limit und Skip Demo ---");
// Erste 3 Studenten
List<Student> ersteDrei = studenten.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println("Erste 3 Studenten:");
ersteDrei.forEach(s -> System.out.println(" " + s.getName()));
// Überspringen der ersten 2
List<Student> nachUeberspringen = studenten.stream()
.skip(2)
.collect(Collectors.toList());
System.out.println("\nNach Überspringen der ersten 2:");
nachUeberspringen.forEach(s -> System.out.println(" " + s.getName()));
// Pagination (Seite 2, 2 Elemente pro Seite)
int seite = 2;
int groesse = 2;
List<Student> paginiert = studenten.stream()
.skip((seite - 1) * groesse)
.limit(groesse)
.collect(Collectors.toList());
System.out.println("\nSeite " + seite + " (Größe " + groesse + "):");
paginiert.forEach(s -> System.out.println(" " + s.getName()));
}
private static void distinctDemo() {
System.out.println("\n--- Distinct Demo ---");
List<Integer> zahlenMitDuplikaten = Arrays.asList(1, 2, 2, 3, 4, 4, 4, 5, 1);
List<Integer> eindeutigeZahlen = zahlenMitDuplikaten.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Mit Duplikaten: " + zahlenMitDuplikaten);
System.out.println("Eindeutig: " + eindeutigeZahlen);
// Distinct mit Objekten
List<String> faecher = Arrays.asList("Informatik", "Mathematik", "Informatik",
"Physik", "Mathematik", "Informatik");
List<String> eindeutigeFaecher = faecher.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("\nFächer mit Duplikaten: " + faecher);
System.out.println("Eindeutige Fächer: " + eindeutigeFaecher);
}
private static void matchDemo(List<Student> studenten) {
System.out.println("\n--- Match Demo ---");
// All Match - alle erfüllen Bedingung
boolean alleBestanden = studenten.stream()
.allMatch(s -> s.getNote() >= 50);
System.out.println("Alle bestanden: " + alleBestanden);
boolean alleInformatik = studenten.stream()
.allMatch(s -> s.getFach().equals("Informatik"));
System.out.println("Alle Informatik: " + alleInformatik);
// Any Match - mindestens einer erfüllt Bedingung
boolean jemandInformatik = studenten.stream()
.anyMatch(s -> s.getFach().equals("Informatik"));
System.out.println("Jemand Informatik: " + jemandInformatik);
boolean jemandSehrGut = studenten.stream()
.anyMatch(s -> s.getNote() >= 90);
System.out.println("Jemand sehr gut: " + jemandSehrGut);
// None Match - keiner erfüllt Bedingung
boolean niemandDurchgefallen = studenten.stream()
.noneMatch(s -> s.getNote() < 50);
System.out.println("Niemand durchgefallen: " + niemandDurchgefallen);
}
private static void findDemo(List<Student> studenten) {
System.out.println("\n--- Find Demo ---");
// Find First - erstes Element
Optional<Student> erster = studenten.stream()
.findFirst();
erster.ifPresent(s -> System.out.println("Erster Student: " + s.getName()));
// Find Any - irgendein Element (besonders bei Parallel Streams)
Optional<Student> irgendeinInformatiker = studenten.stream()
.filter(s -> s.getFach().equals("Informatik"))
.findAny();
irgendeinInformatiker.ifPresent(s ->
System.out.println("Irgendein Informatiker: " + s.getName()));
// Find mit komplexem Prädikat
Optional<Student> besterMathematiker = studenten.stream()
.filter(s -> s.getFach().equals("Mathematik"))
.max(Comparator.comparing(Student::getNote));
besterMathematiker.ifPresent(s ->
System.out.println("Bester Mathematiker: " + s.getName() + " (" + s.getNote() + ")"));
}
private static void optionalDemo(List<Student> studenten) {
System.out.println("\n--- Optional Handling Demo ---");
// Optional mit Map
Optional<String> ersterName = studenten.stream()
.findFirst()
.map(Student::getName);
ersterName.ifPresent(name -> System.out.println("Erster Name: " + name));
// Optional mit Filter
Optional<Student> besterStudent = studenten.stream()
.max(Comparator.comparing(Student::getNote));
String besterName = besterStudent
.filter(s -> s.getNote() >= 90)
.map(Student::getName)
.orElse("Keiner mit 90+ Punkten");
System.out.println("Bester Student (90+): " + besterName);
// Optional Chaining
Optional<String> fachDesBesten = studenten.stream()
.max(Comparator.comparing(Student::getNote))
.flatMap(s -> Optional.ofNullable(s.getFach()))
.map(String::toUpperCase);
fachDesBesten.ifPresent(fach ->
System.out.println("Fach des Besten: " + fach));
// Optional mit Supplier
String defaultValue = studenten.stream()
.filter(s -> s.getName().equals("NichtExistent"))
.findFirst()
.map(Student::getName)
.orElseGet(() -> "Standard-Student");
System.out.println("Default Wert: " + defaultValue);
}
private static void parallelStreamDemo(List<Student> studenten) {
System.out.println("\n--- Parallel Stream Demo ---");
// Parallele Verarbeitung
long startZeit = System.currentTimeMillis();
List<String> namenParallel = studenten.parallelStream()
.filter(s -> s.getNote() > 80)
.map(Student::getName)
.sorted()
.collect(Collectors.toList());
long endZeit = System.currentTimeMillis();
System.out.println("Parallel Ergebnis: " + namenParallel);
System.out.println("Parallel Zeit: " + (endZeit - startZeit) + "ms");
// Vergleich mit sequentieller Verarbeitung
startZeit = System.currentTimeMillis();
List<String> namenSequentiell = studenten.stream()
.filter(s -> s.getNote() > 80)
.map(Student::getName)
.sorted()
.collect(Collectors.toList());
endZeit = System.currentTimeMillis();
System.out.println("\nSequentiell Ergebnis: " + namenSequentiell);
System.out.println("Sequentiell Zeit: " + (endZeit - startZeit) + "ms");
// Thread-Info bei Parallel Stream
System.out.println("\nThread-Info bei Parallel Stream:");
studenten.parallelStream()
.forEach(s -> System.out.println(s.getName() + " auf " +
Thread.currentThread().getName()));
}
// Student-Klasse
static class Student {
private String name;
private String fach;
private int note;
private int semester;
public Student(String name, String fach, int note, int semester) {
this.name = name;
this.fach = fach;
this.note = note;
this.semester = semester;
}
public String getName() { return name; }
public String getFach() { return fach; }
public int getNote() { return note; }
public int getSemester() { return semester; }
}
}
Этот пример демонстрирует целый набор продвинутых операций со Stream, которые помогут вам работать с коллекциями на более высоком уровне абстракции.
Сортировка
Начнём с сортировки. Метод sorted() позволяет упорядочить элементы по определённым критериям. В примере выше мы сортируем студентов по оценкам в возрастающем порядке, затем в убывающем. Если нужна многоуровневая сортировка, можно использовать thenComparing() для добавления дополнительных критериев.
Limit и Skip
Операции limit() и skip() полезны для постраничной выборки данных. limit(n) возвращает только первые n элементов, а skip(n) пропускает первые n элементов. Вместе они образуют простую, но эффективную основу для пагинации.
Distinct
Метод distinct() удаляет дубликаты из потока. Для примитивных типов это работает на основе значения, для объектов проверяется метод equals().
Match-операции
Семейство методов match возвращает булево значение:
allMatch()проверяет, что все элементы удовлетворяют условию.anyMatch()проверяет, что хотя бы один элемент удовлетворяет условию.noneMatch()проверяет, что ни один элемент не удовлетворяет условию.
Find-операции
findFirst() и findAny() возвращают Optional с первым найденным элементом или любым элементом соответственно. Это удобно для получения одного элемента из потока без сбора всех результатов в список.
Optional-handling
Optional позволяет безопасно работать с потенциально пустыми значениями. Методы вроде map(), filter(), flatMap() позволяют строить цепочки преобразований без множества проверок на null.
Параллельные потоки
parallelStream() обрабатывает данные в несколько потоков, используя ForkJoinPool. Это может ускорить обработку больших объёмов данных, но имеет смысл только при достаточно больших коллекциях, так как накладные расходы на параллелизацию могут быть выше, чем выигрыш.
3. Специализированные Collectors и пользовательские операции
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class SpezialisierteCollectors {
public static void main(String[] args) {
System.out.println("=== Spezialisierte Collectors Demo ===");
// Testdaten
List<Produkt> produkte = Arrays.asList(
new Produkt("Laptop", "Elektronik", 999.99, 5),
new Produkt("Maus", "Elektronik", 29.99, 15),
new Produkt("Tastatur", "Elektronik", 79.99, 8),
new Produkt("Buch", "Bücher", 19.99, 20),
new Produkt("Stift", "Büro", 2.99, 50),
new Produkt("Papier", "Büro", 9.99, 30)
);
// Gruppierung с агрегацией
gruppierungMitAggregation(produkte);
// Многоуровневая группировка
multiLevelGruppierung(produkte);
// Пользовательский Collector
customCollectorDemo();
// Downstream Collectors
downstreamCollectorsDemo(produkte);
// Primitive Streams
primitiveStreamsDemo();
}
private static void gruppierungMitAggregation(List<Produkt> produkte) {
System.out.println("\n--- Gruppierung mit Aggregation ---");
// Группировка по категориям со статистикой
Map<String, DoubleSummaryStatistics> preisStatistik = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.summarizingDouble(Produkt::getPreis)
));
preisStatistik.forEach((kategorie, statistik) -> {
System.out.println(kategorie + ":");
System.out.println(" Durchschnitt: " + statistik.getAverage());
System.out.println(" Minimum: " + statistik.getMin());
System.out.println(" Maximum: " + statistik.getMax());
System.out.println(" Summe: " + statistik.getSum());
});
// Группировка с преобразованием
Map<String, Set<String>> kategorieNamen = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.mapping(Produkt::getName, Collectors.toSet())
));
System.out.println("\nКатегории с названиями товаров:");
kategorieNamen.forEach((kategorie, namen) ->
System.out.println(kategorie + ": " + namen));
// Группировка с фильтром
Map<String, List<Produkt>> teureProdukte = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.filtering(p -> p.getPreis() > 50, Collectors.toList())
));
System.out.println("\nДорогие товары (>50€):");
teureProdukte.forEach((kategorie, produkteListe) -> {
if (!produkteListe.isEmpty()) {
System.out.println(kategorie + ": " +
produkteListe.stream().map(Produkt::getName).collect(Collectors.toList()));
}
});
}
private static void multiLevelGruppierung(List<Produkt> produkte) {
System.out.println("\n--- Multi-Level Gruppierung ---");
// Группируем товары по ценовым категориям
Map<String, Map<String, List<Produkt>>> multiLevel = produkte.stream()
.collect(Collectors.groupingBy(
p -> p.getPreis() < 50 ? "Günstig" : "Teuer",
Collectors.groupingBy(Produkt::getKategorie)
));
System.out.println("Multi-Level Gruppierung:");
multiLevel.forEach((preisKategorie, kategorieMap) -> {
System.out.println(preisKategorie + ":");
kategorieMap.forEach((kategorie, produkteListe) -> {
System.out.println(" " + kategorie + ": " +
produkteListe.stream().map(Produkt::getName).collect(Collectors.toList()));
});
});
}
private static void customCollectorDemo() {
System.out.println("\n--- Custom Collector Demo ---");
List<String> woerter = Arrays.asList("Java", "Stream", "API", "Functional", "Programming");
// Пользовательский Collector для конкатенации строк с разделителем
Collector<String, StringBuilder, String> customStringCollector = Collector.of(
StringBuilder::new, // Supplier
(builder, str) -> { // Accumulator
if (builder.length() > 0) {
builder.append(" | ");
}
builder.append(str.toUpperCase());
},
StringBuilder::append, // Combiner
StringBuilder::toString, // Finisher
Characteristics.IDENTITY_FINISH
);
String ergebnis = woerter.stream().collect(customStringCollector);
System.out.println("Custom Collector Ergebnis: " + ergebnis);
// Пользовательский Collector для расчета среднего значения
Collector<Integer, int[], Double> averageCollector = Collector.of(
() -> new int[2], // [sum, count]
(acc, num) -> {
acc[0] += num; // sum
acc[1]++; // count
},
(acc1, acc2) -> {
acc1[0] += acc2[0];
acc1[1] += acc2[1];
return acc1;
},
acc -> acc[1] == 0 ? 0 : (double) acc[0] / acc[1] // average
);
List<Integer> zahlen = Arrays.asList(10, 20, 30, 40, 50);
double durchschnitt = zahlen.stream().collect(averageCollector);
System.out.println("Custom Average: " + durchschnitt);
}
private static void downstreamCollectorsDemo(List<Produkt> produkte) {
System.out.println("\n--- Downstream Collectors Demo ---");
// GroupingBy с подсчётом элементов
Map<String, Long> anzahlProKategorie = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.counting()
));
System.out.println("Количество товаров по категориям:");
anzahlProKategorie.forEach((kategorie, anzahl) ->
System.out.println(kategorie + ": " + anzahl));
// GroupingBy с суммированием
Map<String, Integer> lagerProKategorie = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.summingInt(Produkt::getLagerbestand)
));
System.out.println("\nОстаток на складе по категориям:");
lagerProKategorie.forEach((kategorie, bestand) ->
System.out.println(kategorie + ": " + bestand));
// GroupingBy с поиском максимума
Map<String, Optional<Produkt>> teuerstesProKategorie = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.maxBy(Comparator.comparing(Produkt::getPreis))
));
System.out.println("\nСамый дорогой товар в каждой категории:");
teuerstesProKategorie.forEach((kategorie, optional) ->
optional.ifPresent(p -> System.out.println(kategorie + ": " + p.getName())));
// CollectingAndThen для обработки результатов
Map<String, String> kategorieInfo = produkte.stream()
.collect(Collectors.groupingBy(
Produkt::getKategorie,
Collectors.collectingAndThen(
Collectors.toList(),
liste -> liste.size() + " Produkte, " +
String.format("%.2f€",
liste.stream().mapToDouble(Produkt::getPreis).average().orElse(0))
)
));
System.out.println("\nИнформация по категориям:");
kategorieInfo.forEach((kategorie, info) -> System.out.println(kategorie + ": " + info));
}
private static void primitiveStreamsDemo() {
System.out.println("\n--- Primitive Streams Demo ---");
// IntStream
IntStream zahlen = IntStream.range(1, 10);
int summe = zahlen.sum();
System.out.println("Summe 1-9: " + summe);
// Преобразование в поток объектов через boxed
List<Integer> zahlenListe = IntStream.rangeClosed(1, 5)
.boxed()
.collect(Collectors.toList());
System.out.println("Zahlen als List: " + zahlenListe);
// DoubleStream для вычислений
double[] preise = {19.99, 29.99, 99.99, 149.99};
DoubleSummaryStatistics preisStatistik = Arrays.stream(preise)
.summaryStatistics();
System.out.println("\nСтатистика цен:");
System.out.println(" Anzahl: " + preisStatistik.getCount());
System.out.println(" Summe: " + preisStatistik.getSum());
System.out.println(" Durchschnitt: " + preisStatistik.getAverage());
System.out.println(" Min: " + preisStatistik.getMin());
System.out.println(" Max: " + preisStatistik.getMax());
// LongStream для больших чисел
long fakultaet = LongStream.rangeClosed(1, 10)
.reduce(1, (a, b) -> a * b);
System.out.println("\n10! = " + fakultaet);
// Primitive Stream с фильтрацией
long geradeZahlen = IntStream.rangeClosed(1, 20)
.filter(n -> n % 2 == 0)
.count();
System.out.println("Gerade Zahlen 1-20: " + geradeZahlen);
// MapToObj для преобразования в объекты
List<String> zahlenAlsStrings = IntStream.rangeClosed(1, 5)
.mapToObj(n -> "Zahl " + n)
.collect(Collectors.toList());
System.out.println("Zahlen als Strings: " + zahlenAlsStrings);
}
// Produkt-Klasse
static class Produkt {
private String name;
private String kategorie;
private double preis;
private int lagerbestand;
public Produkt(String name, String kategorie, double preis, int lagerbestand) {
this.name = name;
this.kategorie = kategorie;
this.preis = preis;
this.lagerbestand = lagerbestand;
}
public String getName() { return name; }
public String getKategorie() { return kategorie; }
public double getPreis() { return preis; }
public int getLagerbestand() { return lagerbestand; }
}
}
Обзор функциональных интерфейсов
| Интерфейс | Метод | Описание | Пример |
|---|---|---|---|
Predicate<T> | boolean test(T t) | Проверка условия | s -> s.length() > 5 |
Function<T,R> | R apply(T t) | Преобразование | s -> s.toUpperCase() |
Consumer<T> | void accept(T t) | Обработка | System.out::println |
Supplier<T> | T get() | Предоставление | () -> new Random() |
UnaryOperator<T> | T apply(T t) | Унарная операция | x -> x * x |
BinaryOperator<T> | T apply(T t1, T t2) | Бинарная операция | (a, b) -> a + b |
Обзор Stream-операций
Промежуточные операции (ленивые)
// Filter
stream.filter(x -> x > 0)
// Map
stream.map(x -> x * 2)
// FlatMap
stream.flatMap(list -> list.stream())
// Sorted
stream.sorted()
stream.sorted(Comparator.reverseOrder())
// Distinct
stream.distinct()
// Limit/Skip
stream.limit(10)
stream.skip(5)
// Peek (для отладки)
stream.peek(System.out::println)
Терминальные операции (немедленные)
// ForEach
stream.forEach(System.out::println)
// Collect
stream.collect(Collectors.toList())
// Reduce
stream.reduce((a, b) -> a + b)
// Count
stream.count()
// Min/Max
stream.min(Comparator.naturalOrder())
stream.max(Comparator.reverseOrder())
// Match
stream.anyMatch(x -> x > 0)
stream.allMatch(x -> x > 0)
stream.noneMatch(x -> x > 0)
// Find
stream.findFirst()
stream.findAny()
Типы ссылок на методы
Ссылка на статический метод
// Lambda: s -> Integer.parseInt(s)
// Ссылка на метод: Integer::parseInt
list.stream().map(Integer::parseInt)
Ссылка на метод экземпляра
// Lambda: s -> s.toUpperCase()
// Ссылка на метод: String::toUpperCase
list.stream().map(String::toUpperCase)
Ссылка на конструктор
// Lambda: name -> new Person(name)
// Ссылка на конструктор: Person::new
list.stream().map(Person::new)
Соображения производительности
Когда использовать Stream API
- Сложная обработка данных: операции filter, map, reduce
- Читаемость: декларативный код вместо императивных циклов
- Параллелизм: простое переключение на параллельную обработку
- Функциональное программирование: работа с неизменяемыми структурами
Когда избегать Stream API
- Простые операции: традиционные циклы часто быстрее
- Критичный по производительности код: overhead потоков может быть значимым
- Примитивные массивы: специализированные операции часто лучше
- Очень маленькие коллекции: overhead перевешивает выгоду
Преимущества и недостатки
Преимущества Stream API
- Читаемость: декларативный, выразительный синтаксис
- Композиция: легко связывать операции в цепочку
- Параллелизм: простое переключение на параллельную обработку
- Функциональный стиль: поддержка функционального программирования
- Ленивое вычисление: эффективная обработка данных
Недостатки
- Производительность: overhead при простых операциях
- Отладка: сложнее, чем императивные циклы
- Кривая обучения: новые концепции и синтаксис
- Память: промежуточные коллекции могут потреблять память
Частые экзаменационные вопросы
-
В чём разница между промежуточными и терминальными операциями? Промежуточные ленивы и возвращают поток, терминальные немедленны и завершают обработку.
-
Объясните lambda-выражения! Анонимные функции с компактным синтаксисом:
(parameter) -> expressionили(parameter) -> { statements }. -
Когда использовать ссылки на методы? Как более короткую альтернативу lambda-выражениям, когда существует подходящий метод.
-
В чём преимущество параллельных потоков? Автоматическая параллельная обработка на многоядерных системах для лучшей производительности.
Основные источники
- https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
- https://docs.oracle.com/javase/tutorial/collections/streams/
- https://www.baeldung.com/java-8-streams



