Stream.max: примеры (JAVA)

Руководство по использованию Stream.max
Раздел: Потоки данных (Stream API) - терминальные операции
Stream.max(Comparator comparator): Optional

Описание функции Stream.max

В Java метод Stream.max применяется для получения наибольшего элемента из потока элементов по заданному критерию сравнения. Это терминальная операция, возвращающая объект java.util.Optional, который содержит максимальный элемент при его наличии или пустой Optional при пустом потоке.

Сигнатуры и варианты:

// для объектных потоков
Optional Stream.max(Comparator comparator)

// для примитивных потоков
OptionalInt IntStream.max()
OptionalLong LongStream.max()
OptionalDouble DoubleStream.max()

Аргументы:

  • comparator - обязательный объект типа Comparator<? super T>. Определяет порядок сравнения элементов. Не должен быть null. Компаратор должен быть детерминирован и согласован с equals, если требуется корректная семантика сравнения. Если компаратор возвращает значения, нарушающие транзитивность, результат может быть некорректным.

Возвращаемое значение:

  • Для объектного потока возвращается Optional<T>. Пустой Optional означает, что поток не содержал элементов.
  • Для примитивных потоков возвращаются соответствующие Optional-представления: OptionalInt, OptionalLong, OptionalDouble. Они также могут быть пустыми.

Особенности поведения:

  • Операция является терминальной и может быть выполнена как последовательно, так и параллельно.
  • При параллельной обработке элементы сравниваются в рабочие заданиях, а затем результаты агрегируются; компаратор должен корректно работать в многопоточной среде.
  • Если передан null вместо компаратора, возникает NullPointerException.

Примеры использования

Пример 1. Поиск максимального целого в потоке Integer по естественному порядку.

import java.util.*;
import java.util.stream.*;

List list = Arrays.asList(3, 1, 4, 1, 5);
Optional max = list.stream().max(Comparator.naturalOrder());
System.out.println(max);
Optional[5]

Пример 2. Поиск строки с максимальной длиной.

List names = Arrays.asList("Anna", "Elena", "Markus", "Li");
Optional longest = names.stream().max(Comparator.comparingInt(String::length));
System.out.println(longest.orElse("empty"));
Markus

Пример 3. Примитивный поток IntStream.

IntStream s = IntStream.of(10, 7, 9);
OptionalInt oi = s.max();
System.out.println(oi.isPresent() ? oi.getAsInt() : "no elements");
10

Пример 4. Пользовательский класс с компаратором.

class Person { String name; int age; Person(String n, int a){name=n; age=a;} }

List<Person> people = Arrays.asList(new Person("A",30), new Person("B",25));
Optional<Person> oldest = people.stream().max(Comparator.comparingInt(p -> p.age));
System.out.println(oldest.map(p -> p.name + ":" + p.age));
Optional[A:30]

Похожие возможности в Java

  • Collections.max(Collection, Comparator) - возвращает максимальный элемент коллекции без использования Stream API. Удобно для уже существующих коллекций. Применение: когда поток не нужен.
  • Collectors.maxBy - позволяет вынести логику сравнения в сборщик, например при группировке: collect(Collectors.maxBy(...)). Удобно при агрегациях.
  • Stream.reduce с бинарным оператором - альтернатива для объектных потоков: stream().reduce(BinaryOperator.maxBy(comparator)). Возвращает Optional и допускает более явный контроль над процессом агрегирования.
  • Arrays.stream(...).max() - используется для массивов; по сути тот же механизм, что и у потоков.

Когда выбирать:

  • Если объект уже в коллекции и нужен простой результат, Collections.max удобнее.
  • При группировке с последующим выбором максимума предпочтительнее Collectors.maxBy.
  • Если требуется кастомная логика агрегирования, reduce дает гибкость.

Альтернативы в других языках

Краткое сравнение с примерами.

JavaScript

// массив чисел
const arr = [3,1,4,1,5];
const max = Math.max(...arr);
console.log(max);
5

Для объектов можно использовать reduce:

const people = [{name:'A',age:30},{name:'B',age:25}];
const oldest = people.reduce((a,b) => (a.age > b.age ? a : b));
console.log(oldest);
{ name: 'A', age: 30 }

Python

# список чисел
arr = [3,1,4,1,5]
print(max(arr))

# по ключу для объектов
people = [{'name':'A','age':30},{'name':'B','age':25}]
print(max(people, key=lambda p: p['age']))
5
{'name': 'A', 'age': 30}

PHP

$arr = [3,1,4,1,5];
echo max($arr);
5

SQL

SELECT MAX(salary) FROM employees;
-- возвращает максимальную зарплату в таблице

C# (LINQ)

var arr = new[] {3,1,4,1,5};
Console.WriteLine(arr.Max());

var people = new[]{ new {Name="A", Age=30}, new {Name="B", Age=25} };
var oldest = people.OrderByDescending(p => p.Age).First();
Console.WriteLine(oldest);
5
{ Name = A, Age = 30 }

Go

package main
import "fmt"
func main(){
  arr := []int{3,1,4,1,5}
  max := arr[0]
  for _, v := range arr { if v > max { max = v } }
  fmt.Println(max)
}
5

Kotlin

val arr = listOf(3,1,4,1,5)
println(arr.maxOrNull())

data class Person(val name:String, val age:Int)
val people = listOf(Person("A",30), Person("B",25))
println(people.maxByOrNull { it.age })
5
Person(name=A, age=30)

Lua

local arr = {3,1,4,1,5}
local max = arr[1]
for i=1,#arr do if arr[i] > max then max = arr[i] end end
print(max)
5

Отличия от Java:

  • Во многих языках есть встроенная функция max с параметром key/selector (Python, Kotlin). Это ближе к Comparator в Java, но синтаксис проще.
  • В Java для объектных потоков требуется передать Comparator; для примитивных потоков есть специализированные методы.
  • В функционально ориентированных языках часто присутствует reduce для объектов, аналогичный Stream.reduce в Java.

Типичные ошибки при использовании

1) Передача null в качестве компаратора.

List<Integer> list = Arrays.asList(1,2,3);
list.stream().max(null);
Exception in thread "main" java.lang.NullPointerException
    at java.util.Objects.requireNonNull(Objects.java:...) ...

2) Вызов Optional.get() на пустом Optional приведет к исключению.

Optional<Integer> empty = Stream.<Integer>empty().max(Comparator.naturalOrder());
Integer v = empty.get(); // опасно
Exception in thread "main" java.util.NoSuchElementException: No value present
    at java.util.Optional.get(Optional.java:...)

3) Некорректный компаратор (например, не транзитивный) может дать неожиданный результат.

// некоррективный компаратор
Comparator<Integer> bad = (a,b) -> a%2 - b%2; // не дает полного порядка
List<Integer> list = Arrays.asList(1,2,3);
Optional<Integer> m = list.stream().max(bad);
System.out.println(m);
// Результат непредсказуем в зависимости от внутренней агрегации

4) Ожидание короткого замыкания. max не является короткозамыкающей операцией; чтобы получить результат, поток должен быть обработан полностью.

Изменения в реализации и API

Метод Stream.max был введен в Java 8 вместе с Stream API. Для примитивных потоков соответствующие методы IntStream.max, LongStream.max, DoubleStream.max также появились в Java 8. В последующих версиях Java сигнатуры метода не претерпели существенных изменений. Улучшения касались в основном производительности и оптимизации реализации внутри JVM, а также сопутствующих улучшений Optional и Stream API, но публичный контракт остался прежним.

Расширенные и редкие сценарии использования

1) Максимум по нескольким критериям с использованием thenComparing.

Пример java
record Item(String name, int price, int rating) {}
List<Item> items = List.of(new Item("A",100,4), new Item("B",100,5), new Item("C",120,3));
Optional<Item> best = items.stream().max(
    Comparator.comparingInt(Item::price).thenComparing(Comparator.comparingInt(Item::rating))
);
System.out.println(best.map(i -> i.name + ":" + i.price + "," + i.rating));
Optional[B:100,5]

2) Обработка null-значений через Comparator.nullsFirst/Last.

Пример java
List<String> arr = Arrays.asList(null, "aa", "b");
Optional<String> max = arr.stream().max(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println(max);
Optional[null]

3) Параллельный поток и сбор максимума.

Пример java
List<Integer> big = IntStream.rangeClosed(1, 1_000_000).boxed().collect(Collectors.toList());
Optional<Integer> maxPar = big.parallelStream().max(Comparator.naturalOrder());
System.out.println(maxPar.get());
1000000

4) Нахождение максимального элемента без явного компаратора для Comparable элементов с помощью reduce.

Пример java
Optional<Integer> maxViaReduce = Stream.of(3,1,4,1,5).reduce(Integer::max);
System.out.println(maxViaReduce);
Optional[5]

5) Использование max вместе с collect(Collectors.groupingBy(...)) чтобы найти максимум в каждой группе.

Пример java
record Sale(String region, int amount) {}
List<Sale> sales = List.of(new Sale("EU",100), new Sale("EU",200), new Sale("US",150));
Map<String, Optional<Sale>> bestByRegion = sales.stream()
  .collect(Collectors.groupingBy(s -> s.region, Collectors.maxBy(Comparator.comparingInt(s -> s.amount))));
System.out.println(bestByRegion);
{EU=Optional[Sale[region=EU, amount=200]], US=Optional[Sale[region=US, amount=150]]}

6) Безопасный доступ к результату с orElse и orElseThrow.

Пример java
Optional<Integer> maybe = Stream.<Integer>empty().max(Comparator.naturalOrder());
int v = maybe.orElse(-1);
System.out.println(v);
-1

7) Комбинация mapping + max: сначала преобразование, затем выбор.

Пример java
List<String> words = List.of("one","three","seven");
Optional<Integer> longestLen = words.stream()
  .map(String::length)
  .max(Comparator.naturalOrder());
System.out.println(longestLen);
Optional[5]

8) Поиск максимума среди объектов с несколькими полями и сложной логикой сравнения (например, относительная важность полей).

Пример java
record Candidate(String name, int score, boolean vip) {}
List<Candidate> c = List.of(new Candidate("A",90,false), new Candidate("B",88,true));
Comparator<Candidate> cmp = Comparator.comparing((Candidate x) -> x.vip).reversed()
    .thenComparingInt(x -> x.score);
Optional<Candidate> bestCand = c.stream().max(cmp);
System.out.println(bestCand);
Optional[Candidate[name=B, score=88, vip=true]]

9) Использование max в сочетании с Optional.map для немедленной обработки результата.

Пример java
Stream.of("a","ab","abc").max(Comparator.comparingInt(String::length))
  .map(String::toUpperCase)
  .ifPresent(System.out::println);
ABC

джава Stream.max function comments

En
Stream.max Returns the maximum element of the stream according to the provided Comparator