Технологии Java

Лямбда-выражения и потоки

Лямбда-выражения

Содержание

Функциональный интерфейс

Лямбда-выражения

  • Реализация функционального интерфейса
    • R BiFunction<T, U, R>.apply(t, u) – бинарная функция
    • BiFunction<String, Long, String> f =
          (String s, Long v) -> {
              return s + v;
          }
      
    • BiFunction<String, Long, String> f =
          (String s, Long v) -> s + v;
      
    • BiFunction<String, Long, String> f = (s, v) -> s + v;
      
    • Function<String, String> f = s -> s + s;
      
    • Supplier<String> s = () -> "!";
      
    • Consumer<String> c = s -> System.out.println(s + s);
      

Применение лямбда-выражений

  • Композиция
    • default <V> Function<V, R>
              compose(Function<−V, +T> before) {
          return v -> apply(before.apply(v));
      }
      
  • Обратная композиция
    • default <V> Function<T, V>
              andThen(Function<−R, +V> after) {
          return v -> after.apply(apply(v));
      }
      

Ссылки на методы

  • Метод класса
    • Function<String, Integer> f1 = Integer::parseInt;
      // x -> Integer.parseInt(x);
      
  • Метод экземпляра
    • Function<Integer, String> f2 = Integer::toString;
      // x -> x.toString();
      
  • Метод экземпляра объекта
    • Integer i = 2; // Не обязательно final
      Supplier<String> f3 = i::toString; // () -> i.toString();
      
  • Конструктор
    • Function<String, Integer> f4 = Integer::new;
      // s -> new Integer(s);
      

Замыкания

  • Effectively final переменные
    • Ровно одно присваивание
    • Может быть без модификатора final
    • Могут использоваться в лямбда-выражениях
  • Замыкание → объект в куче
    • Разные вызовы → разные объекты
  • Пример
    • /* final */ String hello = "Hello, ";
      Function<String, String> greeter = 
              name -> hello + name;
      

Ограничения лямбда-выражений

  • Захват изменяемых переменных
  • Прозрачный проброс исключений
    • // Не работает, так как write бросает IOException
      Consumer<String> c = writer::write;
      
  • Изменение потока управления
    • collection.forEach(e -> {
          if (e.equals("done")) {
              // Что делать?
          }
      });
      
  • Реализация
    • Лямбды — не классы

Пример. Исключения ввода-вывода

  • @FunctionalInterface
    interface IOSupplier<T> {
        T get() throws IOException;
        static <T> T unchecked(IOSupplier<T> supplier) {
            try {
                return supplier.get();
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }
    }
    
  • List<String> lines = IOSupplier.unchecked(() ->
        Files.readAllLines(Path.of("input.txt")));
    

Основные классы

Содержание

Функции

Основные функц. интерфейсы

Специализации для примитивов

Типы-обертки

Беззнаковые целые

Класс Objects

Коллекции

Содержание

Необязательные значения (1)

Необязательные значения (2)

Сравнения

  • Статические методы интерфейса Comparator
  • Пример
    • courses.sort(
          Comparator.comparingInt(Course::getYear)
              .thenComparingInt(Course::getTerm)
              .thenComparing(
                  Course::getName,
                  String.CASE_INSENSITIVE_ORDER
      ))
      

Коллекции

Отображения

Пример. Подсчет слов

  • Из Scanner
    • Map<String, Integer> map = new HashMap<>();
      while (scanner.hasNext()) {
          map.merge(scanner.next(), 1, Integer::sum);
      }
      
  • Из файла
    • Map<String, Integer> map = new HashMap<>();
      Files.readAllLines(input).forEach(line -> {
          for (String word : line.split("\\s+")) {
              map.merge(word, 1, Integer::sum);
          }
      });
      

Потоки значений

Содержание

Потоки значений

  • Классы Stream<T>, {Int|Long|Double}Stream
  • Набор элементов, обрабатываемый оптом
    • Может не хранить элементы
    • Может быть ленивым
    • Может быть бесконечным
  • Получение
    • Из коллекций и массивов
    • Генераторы
    • Строки из файлов и наборы файлов
    • Случайные потоки

Примеры использования потоков (1)

  • Что вычисляется?
    • string.stream()
          .filter(s -> s.endsWith("s"))
          .mapToInt(String::length)
          .min();
      
    • Длина самой короткой строки, оканчивающейся на s
  • Что вычисляется?
    • string.parallelStream()
          .filter(s -> s.contains("a"))
          .sorted(String.CASE_INSENSITIVE_ORDER)
          .limit(3)
          .collect(Collectors.joining(", "));
      
    • Три минимальных слова, содержащих a

Операции над потоками

  • Создание
  • Промежуточные
    • Порождают поток
    • Ленивые
  • Завершающие
    • Порождают значения
    • Жадные

Создание потоков (1)

Создание потоков (2)

Модификация потоков

Комбинаторы

Получение результата

Коллекторы

Статистика

Преобразование в коллекцию

Пример. Подсчет слов

  • В тексте
    • Map<String, Long> map = words.stream()
          .collect(Collectors.groupingBy(
              String::toUpperCase,
              Collectors.counting()
          ));
      
  • В файле
    • Map<String, Long> map = Files.lines(path)
          .flatMap(line -> Arrays.stream(line.split("\\s+"))
          .collect(Collectors.groupingBy(
              String::toUpperCase,
              Collectors.counting()
          ));
      

Комбинаторы

Преобразование в строку

Реализация потоков

Содержание

Разбиваемые итераторы

Содержание

Разбиваемые итераторы

Иерархия изменяемости

  • Неизменяемый (string.lines())
  • Управляющий изменениями (ConcurrentSkipListSet)
  • С детекцией изменений
    • Раннее связывание
    • Позднее связывание (ArrayList)
  • Без детекции изменений

Детекция изменений

  • Как бы вы реализовали?
  • Счётчик modCount
  • Итераторы
    • Что делать с remove()?
    • Обновить modCount итератора

Обработка изменений

  • Неизменяемый
  • Последовательно изменяемый
    • Явная политика связывания
    • Явная политика обработки изменений
  • Параллельно изменяемый

Свойства элементов

  • Харарактеристики

Число элементов

Специализация для примитивов

Пример: ArrayMap (1)

  • ArrayMap
    • Object[] entries
    • Хранит пары ключ-значение в 2i и 2i+1 элементах
    • public boolean tryAdvance(
          Consumer<? super Entry<K, V>> action
      ) {
          checkForComodification();
          if (i < bound) {
              action.accept(entry(i += 2));
              return true;
          }
          return false;
      }
      

Пример: ArrayMap (2)

  • ArrayMap
    • Object[] entries
    • Хранит пары ключ-значение в 2i и 2i+1 элементах
    • public Spliterator<Entry<K, V>> trySplit() {
          checkForComodification();
          final int left = i;
          final int mid = (left + bound) / 4 * 2;
          if (i < mid) {
              i = mid;
              return new Spliter(left, mid);
          }
          return null;
      }
      

Создание Spliterator

Потоки из Spliterator

Пример: своя операция (1)

  • Сгруппировать элементы по n штук
    • Использование
      // [[a, b, c], [d]]
      chunked(List.of("a", "b", "c", "d", 3))
      
    • Реализация
      Stream<List<T>> chunked(Stream<+T> stream, int n) {
          Spliterator<+T> spliterator = stream.spliterator();
          return StreamSupport.stream(
              new Spliterators.AbstractSpliterator<>(
                      (spliterator.estimateSize() + n - 1) / n,
                      spliterator.characteristics() 
                          & ~Spliterator.SORTED
                          | Spliterator.NONNULL
              )
          );
      }
      

Пример: своя операция (2)

  • Сгруппировать элементы по n штук
    • boolean tryAdvance(Consumer<-List<T>> action) {
          final List<T> chunk = new ArrayList<>();
          while (chunk.size() < n && 
              spliterator.tryAdvance(chunk::add)) {
          }
          if (!chunk.isEmpty()) {
              action.accept(chunk);
          }
          return !chunk.isEmpty();
      }
      

Обобщенные свёртки

Общая идея

  • Разбить на куски
  • Последовательно обработать кусок
    • zero: () → A
    • op: (A, T) → A
    • (...((zero() op v0) op v1) ... op vn)
  • Склеить результаты кусков
    • combine: (A, A) → A
  • Получить результат
    • finisher: A → R

Коллекторы

Параллельное исполнение

  • Явное состояния
    • Только в A
  • Невмешательство (non-iterference)
    • Нельзя изменять обрабатываемые данные
  • Без побочных эффектов
    • Нельзя изменять контекст
  • Упорядоченность
    • Результат – упорядочен
    • Вызовы – нет
  • Повторное использование
    • Значение используется один раз

Пример коллектора (1)

  • Существование элемента
    • <T> Collector<T, ?, Boolean> has(Predicate<-T> p) {
          class State {
              boolean found;
          }
          return Collector.of(
                  State::new,
                  (state, e) -> state.found |= p.test(e),
                  (state1, state2) -> {
                      state1.found |= state2.found;
                      return state1;
                  },
                  state -> state.found
          );
      }
      

Пример коллектора (2)

  • Получение минимальных имени и фамилии
    • Collector<Student, String[], Student>
          MIN_NAMES = Collector.of(
              () -> new String[2],
              (mins, student) -> {
                  mins[0] = min(mins[0], student.getFirstName());
                  mins[1] = min(mins[1], student.getLastName());
              },
              (mins1, mins2) -> {
                  mins1[0] = min(mins1[0], mins2[0]);
                  mins1[1] = min(mins1[1], mins2[1]);
                  return mins1;
              },
              mins -> new Student(mins[0], mins[1])
          );
      

Пример коллектора (3)

  • Объединение коллекторов (1)
    • <T, F, S, FA, SA> 
      Collector<T, Pair<FA, SA>, Pair<F, S>> tee(
          Collector<T, FA, F> c1, Collector<T, SA, S> c2
      ) {
          return Collector.of(
              () -> new Pair<>( // supplier
                  c1.supplier().get(), 
                  c2.supplier().get()
              ),
              (state, value) -> { // accumulator
                  c1.accumulator().accept(state.v1, value);
                  c2.accumulator().accept(state.v2, value);
              },
              (state1, state2) -> new Pair<>( // combiner
                  c1.combiner().apply(state1.v1, state2.v1),
                  c2.combiner().apply(state1.v2, state2.v2)
              ),
              state -> new Pair<>( // finisher
                  c1.finisher().apply(state.v1),
                  c2.finisher().apply(state.v2)
              )
          );
      }
      

Пример коллектора (3)

  • Объединение коллекторов (2)
    • (state1, state2) -> new Pair<>( // combiner
          c1.combiner().apply(state1.v1, state2.v1),
          c2.combiner().apply(state1.v2, state2.v2)
      ),
      state -> new Pair<>( // finisher
          c1.finisher().apply(state.v1),
          c2.finisher().apply(state.v2)
      )
      

Пример коллектора (3)

  • Объединение коллекторов (3)
    • tee(
          Collectors.mapping(Student::firstName, 
              Collectors.minBy(Comparator.naturalOrder())),
          Collectors.mapping(Student::lastName, 
              Collectors.minBy(Comparator.naturalOrder()))
      )
      
  • Стандартный коллектор
    • teeing(
          mapping(Student::firstName, minBy(naturalOrder())),
          mapping(Student::lastName, minBy(naturalOrder())),
          (Optional<String> first, Optional<String> last) -> 
              first.flatMap(f -> last.map(l -> new Student(f, l)))
      )
      

Заключение

Содержание

Что еще есть в java.util (1)

Что еще есть в java.util (2)

Ссылки

Вопросы

???