51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Java Stream 及 JavaScript Reduce 使用方法

前言 {#前言}

距离 4 月 12 日博客建立以来已过 3 个月,连一篇文章都没有写,总想着第一篇文章要写的尽善尽美,力求做到干货满满。但是平时 crud 写多了也没什么积累一时也不知道该写些啥,再加上懒病上身,就这么一拖再拖拖拖。现在觉着,得先解决写不写的问题,再解决好不好的问题。先权当给自己记流水账,代码备忘了。

简介 {#简介}

Reduce 指将一系列的值 通过某种操作逐步合并为单个值的过程。这个操作可以是加法、乘法、逻辑运算或者其他任何能够组合两个值的操作。

也就是说你最终想要在集合或数组的一系列值中得到一个值,那么这个操作就可以用 Reduce 来实现。

Java Stream reduce {#java-stream-reduce}

Java Stream 的 reduce 有 3 个重载方法,分别是:

  1. 一个参数的 reduce
Optional<T> reduce(BinaryOperator<T> accumulator);

参数:BinaryOperator<T> accumulator继承BiFunction<T,T,T>,实现其中的T apply(T t1, T t2);接口即可。其中T为集合中的类型。

返回值:Optional<T>,由于集合为空会返回空值所以需要使用Optional

  1. 两个参数的 reduce
T reduce(T identity, BinaryOperator<T> accumulator);

参数:

  • BinaryOperator<T> accumulator与一个参数的 reduce 相同
  • T identity作用是设置一个初始值(默认值)。当集合为空时,就返回这个默认值,当集合不为空时,该值也会参与计算。其中T为集合中的类型。

返回值:T,由于指定了初始值(默认值),不存在返回空值的情况。

  1. 三个参数的 reduce
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);

其中T为集合中的类型,U为自己指定的其他类型。

参数:

  • U identity作用是设置一个初始值(默认值)。注意:可以指定其它类型的值。
  • BiFunction<U, ? super T, U> accumulator需要实现其中的U apply(U u, T t);接口。
  • BinaryOperator<U> combinerparallelStream()并行流使用,将各个线程中计算的结果合并起来。需要注意的是每个线程都会加一次U identity初始值(默认值)。比方说用parallelStream()并行流对集合进行求和,初始值设置为 1,开启了 3 个线程进行计算,那么这 3 个线程都会加上一次初始值 1,最终合并出来的结果会比实际结果多 2 。

返回值:U,即指定的初始值类型

也就是说,三个参数的reduce()可以返回与集合中的元素不同类型的值,方便我们对复杂对象做计算式和转换。

JavaScript reduce {#javascript-reduce}

  1. 一个参数的 reduce
reduce(callbackFn)

参数:callbackFn为数组中每个元素执行的函数。其返回值将作为下一次调用 callbackFn 时的 accumulator 参数。对于最后一次调用,返回值将作为 reduce() 的返回值。该函数被调用时将传入以下参数:

  • accumulator:上一次调用 callbackFn 的结果。在第一次调用时,如果指定了 initialValue 则为指定的值,否则为 array[0] 的值。
  • currentValue:当前元素的值。在第一次调用时,如果指定了 initialValue,则为 array[0] 的值,否则为 array[1]
  • currentIndexcurrentValue 在数组中的索引位置。在第一次调用时,如果指定了 initialValue 则为 0,否则为 1。
  • array:调用了 reduce() 的数组本身。

即参数callbackFn样式应为:

(accumulator, currentValue, currentIndex, array) => {
    // 一些操作...
}

返回值:使用"reducer"回调函数遍历整个数组后的结果。

  1. 两个参数的 reduce
reduce(callbackFn, initialValue)

参数:

  • callbackFn:与一个参数的 reduce 相同
  • initialValue:第一次调用回调时初始化 accumulator 的值。如果指定了 initialValue,则 callbackFn 从数组中的第一个值作为 currentValue 开始执行。如果没有指定 initialValue,则 accumulator 初始化为数组中的第一个值,并且 callbackFn 从数组中的第二个值作为 currentValue 开始执行。在这种情况下,如果数组为空(没有第一个值可以作为 accumulator 返回),则会抛出错误。

例子 {#例子}

以下对数组的描述,在 Java 中对应List类型,在 JavaScript 中对应Array类型。

求对象数组中值的总和 {#求对象数组中值的总和}

假设待处理的值为:[{ x: 1 }, { x: 2 }, { x: 3 }]

Java {#java}

Integer result = list.stream().reduce(0, (accumulator, currentValue) -> accumulator + currentValue.getX(), Integer::sum);

System.out.println("result = " + result);
/* 输出:
result = 6
*/

JavaScript {#javascript}

const result = arr.reduce((accumulator, currentValue) => accumulator + currentValue.x, 0)

console.log('result =', result)
/* 输出:
result = 6
*/

展平嵌套数组 {#展平嵌套数组}

假设待处理的值为:[[0, 1], [2, 3], [4, 5]]

Java {#java-1}

List<Integer> result = list.stream().reduce(new ArrayList<>(), (accumulator, currentValue) -> {
    accumulator.addAll(currentValue);
    return accumulator;
}, (totalCombine, currentCombine) -> {
    totalCombine.addAll(currentCombine);
    return totalCombine;
});

System.out.println("result = " + result);
/* 输出:
result = [0, 1, 2, 3, 4, 5]
*/

JavaScript {#javascript-1}

const result = arr.reduce((accumulator, currentValue) => accumulator.concat(currentValue), [])

console.log('result = ', result)
/* 输出:
result =  [ 0, 1, 2, 3, 4, 5 ]
*/

统计值的出现次数 {#统计值的出现次数}

假设待处理的值为:["Alice", "Bob", "Tiff", "Bruce", "Alice"]

Java {#java-2}

HashMap<String, Integer> result = list.stream().reduce(new HashMap<>(list.size() * 2), (accumulator, currentValue) -> {
    if (accumulator.containsKey(currentValue)) {
        accumulator.put(currentValue, accumulator.get(currentValue) + 1);
    } else {
        accumulator.put(currentValue, 1);
    }
    
    return accumulator;
}, (totalCombine, currentCombine) -> {
    currentCombine.forEach((key, value) -> {
        if (totalCombine.containsKey(key)) {
            totalCombine.put(key, totalCombine.get(key) + value);
        } else {
            totalCombine.put(key, value);
        }
    });

    return totalCombine;
});

System.out.println("result = " + result);
/* 输出:
result = {Bruce=1, Tiff=1, Bob=1, Alice=2}
 */

JavaScript {#javascript-2}

const result = arr.reduce((accumulator, currentValue) => {
  const count = accumulator[currentValue] ?? 0
  
  return {
    ...accumulator,
    [currentValue]: count + 1
  }
}, {})

console.log('result = ', result)
/* 输出:
result =  { Alice: 2, Bob: 1, Tiff: 1, Bruce: 1 }
*/

按属性对对象进行分组 {#按属性对对象进行分组}

假设初始值为:

[
  { name: "Alice", age: 21 },
  { name: "Max", age: 20 },
  { name: "Jane", age: 20 },
]

Java {#java-3}

使用 Java Stream 中的Collectors.groupingBy()更为简单一些

Map<Integer, List<Item>> result = list.stream().collect(Collectors.groupingBy(Item::getAge));

System.out.println("result = " + result);
/* 输出:
result = {20=[Item(name=Max, age=20), Item(name=Max, age=20)], 21=[Item(name=Alice, age=21)]}
 */

JavaScript {#javascript-3}

/**
 * 按属性对对象进行分组
 *
 * @param {Array} array 待分组的数组
 * @param {*} property 要分组的属性
 */
const groupBy = (array, property) => {
  return array.reduce((accumulator, currentValue) => {
    const key = currentValue[property]
    const group = accumulator[key] ?? []

    return {
      ...accumulator,
      [key]: [...group, currentValue]
    }
  }, {})
}

const result = groupBy(arr, 'age')

console.log('result = ', result)
/* 输出:
result =  {
  '20': [ { name: 'Max', age: 20 }, { name: 'Jane', age: 20 } ],
  '21': [ { name: 'Alice', age: 21 } ]
}
*/

连接包含在对象数组中的数组 {#连接包含在对象数组中的数组}

假设初始值为:

[
    {
        "name":"Anna",
        "books":[
            "Bible",
            "Harry Potter"
        ],
        "age":21
    },
    {
        "name":"Bob",
        "books":[
            "War and peace",
            "Romeo and Juliet"
        ],
        "age":26
    },
    {
        "name":"Alice",
        "books":[
            "The Lord of the Rings",
            "The Shining"
        ],
        "age":18
    }
]

连接其中的books对象字段

Java {#java-4}

List<String> result = list.stream().reduce(new ArrayList<>(), (accumulator, currentValue) -> {
    accumulator.addAll(currentValue.getBooks());
    return accumulator;
}, (totalCombine, currentCombine) -> {
    totalCombine.addAll(currentCombine);
    return totalCombine;
});

System.out.println("result = " + result);
/* 输出:
result = [Bible, Harry Potter, War and peace, Romeo and Juliet, The Lord of the Rings, The Shining]
 */

JavaScript {#javascript-4}

const result = arr.reduce((accumulator, currentValue) => [...accumulator, ...currentValue.books], [])

console.log('result = ', result)
/* 输出:
result =  [
  'Bible',
  'Harry Potter',
  'War and peace',
  'Romeo and Juliet',
  'The Lord of the Rings',
  'The Shining'
]
*/

数组去重 {#数组去重}

假设初始值为:["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"]

Java {#java-5}

使用 Java Stream 中的distinct()更为简单一些

List<String> result = list.stream().distinct().collect(Collectors.toList());

System.out.println("result = " + result);
/* 输出:
result = [a, b, c, e, d]
 */

JavaScript {#javascript-5}

const result = arr.reduce((accumulator, currentValue) => {
  if (!accumulator.includes(currentValue)) {
    return [...accumulator, currentValue]
  }

  return accumulator
}, [])

console.log('result = ', result)
/* 输出:
result =  [ 'a', 'b', 'c', 'e', 'd' ]
*/

使用 reduce() 来替代 .filter().map() {#使用-reduce-来替代-filtermap}

使用filter()map()会遍历数组两次,但是你可以使用reduce()只遍历一次并实现相同的效果,从而更高效。

假设初始值为:[-5, 6, 2, 0]

假设目标为:只保留其中大于 0 的元素并乘以 2

Java {#java-6}

List<Integer> result = list.stream().reduce(new ArrayList<>(), (accumulator, currentValue) -> {
    if (currentValue > 0) {
        accumulator.add(currentValue * 2);
    }

    return accumulator;
}, (totalCombine, currentCombine) -> {
    totalCombine.addAll(currentCombine);

    return totalCombine;
});

System.out.println("result = " + result);
/* 输出:
result = [12, 4]
 */

JavaScript {#javascript-6}

const result = arr.reduce((accumulator, currentValue) => {
  if (currentValue > 0) {
    return [...accumulator, currentValue * 2]
  }

  return accumulator
}, [])

console.log('result = ', result)
/* 输出:
result =  [ 12, 4 ]
*/

参考资料 {#参考资料}

赞(3)
未经允许不得转载:工具盒子 » Java Stream 及 JavaScript Reduce 使用方法