Тщательно понять сокращение в Java8

Java

Пусть в мире не будет сложной Java для изучения

Нравится, Следуй, Избранное

1. Введение

  • что такое уменьшить? Короче говоря, я думаю о редукции как о нормализованной итерационной операции. Берет поток и объединяет их в простой результат, многократно применяя операции.

  • Если вы хотите сравнить его с collect, обычно collect возвращает List, Set, Map..., тогда как reduce обычно возвращает только T (но T является универсальным, на самом деле вы можете возвращать любой тип, включая Список в классе).

  • В этой статье в основном представлена ​​функция reduce() с тремя параметрами: parallel, non-thread-safe и Combiner.

Optional<T> reduce(BinaryOperator<T> accumulator);

T reduce(T identity,BinaryOperator<T> accumulator);

<U> U  reduce(U identity,BiFunction<U,? super T,U> accumulator,BinaryOperator<U> combiner);

2, небольшой тестовый нож

  • сократить одним аргументом
  • Среди них нижние уровни, такие как sum, min и max, реализованы с помощью сокращения.
/***
* @param: accumulator
* @return:  Optional
*/
Optional<T> reduce(BinaryOperator<T> accumulator);
  • Среди них BinaryOperator можно рассматривать как частный случай BiFunction.
public class Main {

  public static void main(String[] args) {
  
  
  List<Integer> list = Lists.newArrayList(1,2,3,4,5);
  
  list.stream().reduce( 
        new BinaryOperator<Integer>() {
          @Override
          public Integer apply(Integer integer, Integer integer2) {
            return integer + integer2;
          }
        }));
  
    //=====等价于=====
    System.out.println(IntStream.range(1, 100).reduce((v1, v2) -> v1 + v2).orElse(0));
  //=====等价于=====
    System.out.println(IntStream.range(1, 100).reduce(Integer::sum).orElse(0));
  }
}

integer=1===integer2=2
integer=3===integer2=3
integer=6===integer2=4
integer=10===integer2=5

3. Продемонстрируйте свои навыки

  • Сокращение двух параметров на самом деле на одно начальное значение больше, чем на один параметр. То есть первый параметр сокращения указывается для первой итерации.
public class Main {

  public static void main(String[] args) {

    List<Integer> list = Lists.newArrayList(1,2,3,4,5);
    
    //初始值100
    list.stream().reduce(
        100,
        new BinaryOperator<Integer>() {
          @Override
          public Integer apply(Integer integer, Integer integer2) {
            System.out.println("integer="+integer+"===integer2="+integer2);
            return integer + integer2;
          }
        }));
  }
}
//初始值是100,不是1。比没有初始值的reduce多迭代1次。
integer=100===integer2=1
integer=101===integer2=2
integer=103===integer2=3
integer=106===integer2=4
integer=110===integer2=5

4. Расправить крылья и парить

  • Первые два — это миньоны, а снижение на три параметра — это большой БОСС.
  • Позвольте мне сначала сделать вывод, третий параметр бесполезен при непараллельном потоке. Когда в потоке только один элемент, распараллеливание не будет выполняться, даже если указано parallel.
  • При использовании сокращения с тремя параметрами обязательно обратите внимание на безопасность потоков.
<U> U  reduce(U identity,BiFunction<U,? super T,U> accumulator,BinaryOperator<U> combiner);

  • Краткое понимание BiFunction, первый параметр T, второй параметр U, возвращаемое значение R
=====> (T t,U u)-> (R)r  
@FunctionalInterface
public interface BiFunction<T, U, R> {

    R apply(T t, U u);

    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}
  • Взгляните на демо ниже (рекомендуется потратить несколько минут на размышления, возможно, вы все поймете)
public static void main(String[] args) {
    ArrayList<Integer> accResult = Stream.of(1, 3, 5, 7)
        .reduce(new ArrayList<>(),
            new BiFunction<ArrayList<Integer>, Integer, ArrayList<Integer>>() {
              @Override
              public ArrayList<Integer> apply(ArrayList<Integer> integers, Integer item) {
                System.out.println("before add: " + integers);
                System.out.println("item= " + item);
                integers.add(item);
                System.out.println("after add : " + integers);
                System.out.println("In BiFunction");
                return integers;
              }
            }, new BinaryOperator<ArrayList<Integer>>() {
              @Override
              public ArrayList<Integer> apply(ArrayList<Integer> integers,
                  ArrayList<Integer> integers2) {
                integers.addAll(integers2);
                System.out.println("integers: " + integers);
                System.out.println("integers2: " + integers2);
                System.out.println("In BinaryOperator");
                return integers;
              }
            });
    System.out.println("accResult: " + accResult);
  }
  • Содержимое третьего параметра вообще не печатается? Тогда зачем вам третий параметр, не переживайте
public static void main(String[] args) {
    ArrayList<Integer> accResult = Stream.of(1, 3, 5, 7).parallel()
        .reduce(new ArrayList<>(),
            new BiFunction<ArrayList<Integer>, Integer, ArrayList<Integer>>() {
              @Override
              public ArrayList<Integer> apply(ArrayList<Integer> integers, Integer item) {
                integers.add(item);
                return integers;
              }
            }, new BinaryOperator<ArrayList<Integer>>() {
              @Override
              public ArrayList<Integer> apply(ArrayList<Integer> integers,
                  ArrayList<Integer> integers2) {
                integers.addAll(integers2);
                System.out.println("thread name="+Thread.currentThread().getName()+" ==== integers=" + integers);
                System.out.println("integers2: " + integers2);
                System.out.println("In BinaryOperator");
                return integers;
              }
            });
    //打印结果几乎每次都不同
    System.out.println("accResult: " + accResult);
  }
  • При еще одной параллели результат печати почти каждый раз разный, да и нулей куча. Данные не полные, мои 1, 3, 5 и 7 неполные. Иногда сообщают об исключениях.

5. Сокращение под параллельным потоком

  • Проблемы безопасности потоков возникают при использовании параллельных потоков. Например, ArrayList в приведенном выше примере не является потокобезопасным классом, и многие операции могут привести к неожиданным результатам.

5.1. Параллельное уменьшить под основными типами данных

 public static void main(String[] args) {
    System.out.println(
        Stream.of(1, 2, 3, 4).parallel().reduce(5, new BiFunction<Integer, Integer, Integer>() {
              @Override
              public Integer apply(Integer integer, Integer integer2) {
                return integer + integer2;
              }
            }
            , new BinaryOperator<Integer>() {
              @Override
              public Integer apply(Integer integer, Integer integer2) {
                return integer + integer2;
              }
            }));
  }
  • Принципиальная схема выглядит следующим образом: (где количество накопителей = потоку, количество объединителей на 1 меньше, чем количество накопителей)

5.2 Если первым параметром является объект, такой как ArrayList, а не базовый тип данных или String, результат может сильно отличаться от того, что мы думаем.

  • Результаты печати и имена потоков, скорее всего, будут несогласованными, а ArrayList не является потокобезопасным. (Если Collections.synchronizedList(a) оборачивает элементы, меньше не будет).

  • Сосредоточьтесь на System.out.println(acc==item);. Второй параметр каждого аккумулятора совпадает с ArrayList в первом параметре.

  • Если вы преобразуете List a в Collections.synchronizedList(a), порядок может быть другим, но элементы должны быть завершены.

//Приблизительная схема

6. Резюме

  • Кратко представлены три перегруженные функции сокращения, и особое внимание уделяется сокращению с тремя параметрами.
  • Аккумулятор (третий параметр) бесполезен в непараллельных потоках, количество аккумуляторов в параллельных потоках равно количеству элементов потока, а количество объединителей на единицу меньше числа элементов потока.
  • Потокобезопасность при параллельном потоке. Первый параметр в объединителе — это разница между типами объектов, такими как ArrayList, и базовыми типами.