Stream API

 

Stream API

Специализации:

  • java.util.stream.Stream
  • java.util.stream.IntStream
  • java.util.stream.LongStream
  • java.util.stream.DoubleStream

Stream

  • Поток однотипных данных для однотипной обработки
  • Источник создает ленивый стрим
  • Промежуточные операции описывают рецепт обработки, но ничего не делают
  • Вся работа производится при вызове терминальной операции
  • Может потребить не все элементы
  • Может быть бесконечным или завершиться за конечное время

Пример:

class Main {
    public static void main(String[] args) {
        source.stream()
                .map(x -> x.squash())
                .filter(x -> x.getColor() != YELLOW)
                .forEach(System.out::println);
    }
}

Некоторые источники Stream

  • Stream.empty()
  • Stream.of(x, y, z)
  • Stream.ofNullable(x)
  • Stream.generate(Math::random)
  • Stream.iterate(val, x -> x + 1)
  • Stream.iterate(0, x -> x < 100, x -> x + 1) - Java 9
  • collection.stream()
  • Arrays.stream(array) - int/long/double/Object
  • Random.ints()/longs()/doubles()
  • String.chars()
  • Pattern.aplitAsStream()
  • ….

Промежуточные операции (intermediate)

  • map() mapToInt/mapToLong/mapToDouble/mapToObj
  • filter()
  • flatMap() flatMapToInt/flatMapToLong/flatMapToDouble
  • mapMulti mapMultiToInt/mapMultiToLong/mapMultiToDouble - Java 16
  • distinct()
  • sorted()
  • limit()
  • skip()
  • peek()
  • takeWhile() - Java 9
  • dropWhile() - Java 9
  • boxed asLongStream/asDoubleStream - примитивы

Терминальные операции (terminal)

  • count()
  • findFirst()/findAny -> Optional
  • anyMatch()/noneMatch/allMatch
  • forEach()/forEachOrdered
  • max()/min -> Otional
  • reduce()
  • collect()
  • toArray()
  • toList() - Java 16
  • sum average/summaryStatistics - примитивы

Примеры:

import java.util.stream.Collectors;

class Main {
  // не правильная реализация  
  public List<String> processList(List<String> list) {
    List<String> result = new ArrayList<>();
    list.stream()
            .map(String::trim)
            .filter(s -> !s.isEmpty())
            .forEach(result::add);
    return result;
  }

  // правильная реализация
  public List<String> processList(List<String> list) {
    return list.stream()
            .map(String::trim)
            .filter(s -> !s.isEmpty())
            .collect(Collectors.toList());
  }
}
class Main {
  public static void main(String[] args) {
    List<List<String>> listOfLists = List.of(
            List.of("foo", "bar", "baz"),
            List.of("Java", "Kotlin", "Groovy"),
            List.of("Hello", "World")
    );
    System.out.println(
            listOfLists.stream()
            .flatMap(List::stream)
            .anyMatch("Java"::equals)
    );
  }
}
class Main {
  // reduce
  static BigInteger factorial(int n) {
    return IntStream.rangeClosed(1, n)
            .mapToObj(BigInteger::valueOf)
            .reduce(BigInteger.ONE, BigInteger::multiply);
  }
}

Don’t over reduce!

  • reduce(0, Integer::sum) -> mapToInt(x -> x).sum()
  • reduce(Integer::max) -> max()
  • reduce("", String::concat) -> collect(Collectors.joining())

Collector

  • Рецепт терминальной операции (способ комбинирования элементов в единое целое)
  • Предопределенные коллекторы в классе Collectors
  • Некоторые коллекторы можно комбинировать
  • Свой коллектор с нуля (Collector.of)

Collectors

  • toList() - ArrayList (Не гарантированно)
  • toSet() - HashSet (Не гарантированно)
  • toCollection(supplier)
  • toMap(keyMapper, valueMapper[, merger[, mapSupplier]])
  • joining([separator[, prefix, suffix]])
  • groupingBy(classifier[[, mapSupplier], downstream])
  • partitioningBy(predicate[, downstream])
  • reducing/counting/mapping/minBy/maxBy
  • averagingInt/averagingLong/averagingDouble
  • summingInt/summingLong/summingDouble

Задача. Сгруппировать строки по длине
Пример 1

class Main {
  // Задача. Сгруппировать строки по длине
  static Map<Integer, List<String>> stringsByLength(List<String> list) {
    return list.stream().collect(Collectors.groupingBy(String::length));
  }

  public static void main(String[] args) {
    stringsByLength(Arrays.asList("a", "bb", "c", "dd", "eee"));
    // {1=[a, c], 2=[bb, dd], 3=[eee]}
  }
}

Пример 2

class Main {
  static Map<Integer, String> stringsByLength(List<String> list) {
    return list.stream().collect(Collectors.groupingBy(String::length, Collectors.joining("+")));
  }

  public static void main(String[] args) {
    stringsByLength(Arrays.asList("a", "bb", "c", "dd", "eee"));
    // {1=a+c, 2=bb+dd, 3=eee}
  }
}

Пример 3

class Main {
  static Map<Integer, Map<Character, List<String>>> strByLenAndFirstLetter(List<String> list) {
    return list.stream()
            .collect(Collectors.groupingBy(String::length,
                    Collectors.groupingBy(s -> a.charAt(0))));
  }

  public static void main(String[] args) {
    strByLenAndFirstLetter(Arrays.asList("a", "b", "aa", "ab", "ba"));
    // {1={a=[a], b=[b]}, 2={a=[aa, ab], b=[ba]}
  }
}

Пример с объектами 1:

interface User { 
    String name(); 
}
interface Department {
    String title();
    User chief();
    Stream<User> users();
}
interface Company { 
    Stream<Department> departments(); 
}

class Main {
    // Список отделов по начальнику
    static Map<User, List<Department>> departmentByChief(Company company) {
        return company
                .departments()
                .collect(Collectors.groupingBy(Department::chief));
    }
    
    // Список названий отделов по начальнику
    static Map<User, List<String>> departmentNamesByChief(Company company) {
        return company
                .departments()
                .collect(Collectors.groupingBy(Department::chief,
                        Collectors.mapping(Department::title, toList())));
    }
    
    // Найти подчиненных для каждого начальника
    // Java 9
    static Map<User, Set<User>> supervisors(Company company) {
        return company
                .departments()
                .collect(Collectors.groupingBy(Department::chief, 
                        Collectors.flatMapping(Department::users, toSet())));
    }
}

Пример с объектами 2:

interface Book {
    String getCategory();
    int getPrice();
}
class Main {
    /* Найти самую дешёвую книгу в каждой категории */
  
    // !! Не верное решение!!
    static Map<String, Integer> getLowBookPriseByCategory(List<Book> list) {
        return list.stream()
                .collect(Collectors.groupingBy(Book::getCategory,
                        Collectors.minBy(Comparator.comparingInt(Book::getPrice))));
    }

    // Верное решение
    static Map<String, Integer> getLowBookPriseByCategory(List<Book> list) {
      return list.stream()
              .collect(Collectors.toMap(
                      Book::getCategory, Book::getPrice,
                      BinaryOperator.minBy(Comparator.naturalOrder())
              ));
    }
}