如何在应用某些函数后有效地计算集合的最大值

Pau*_*ton 16 java java-8 java-stream

假设您有一个这样的方法来计算Collection某些方法的最大值ToIntFunction:

static <T> void foo1(Collection<? extends T> collection, ToIntFunction<? super T> function) {
    if (collection.isEmpty())
        throw new NoSuchElementException();
    int max = Integer.MIN_VALUE;
    T maxT = null;
    for (T t : collection) {
        int result = function.applyAsInt(t);
        if (result >= max) {
            max = result;
            maxT = t;
        }
    }
    // do something with maxT
}
Run Code Online (Sandbox Code Playgroud)

使用Java 8,可以将其转换为

static <T> void foo2(Collection<? extends T> collection, ToIntFunction<? super T> function) {
    T maxT = collection.stream()
                       .max(Comparator.comparingInt(function))
                       .get();
    // do something with maxT
}
Run Code Online (Sandbox Code Playgroud)

新版本的缺点function.applyAsInt是对于相同的值重复调用T.(特别是如果集合具有大小n,则foo1调用applyAsInt n时间,然后foo2调用它的2n - 2次数).

第一种方法的缺点是代码不太清晰,您无法修改它以使用并行性.

假设您希望使用并行流来执行此操作,并且applyAsInt每个元素仅调用一次.这可以用简单的方式编写吗?

Tun*_*aki 10

您可以使用自定义收集器来保持运行最大值和最大元素的对:

static <T> void foo3(Collection<? extends T> collection, ToIntFunction<? super T> function) {
    class Pair {
        int max = Integer.MIN_VALUE;
        T maxT = null;
    }
    T maxT = collection.stream().collect(Collector.of(
        Pair::new,
        (p, t) -> {
            int result = function.applyAsInt(t);
            if (result >= p.max) {
                p.max = result;
                p.maxT = t;
            }
        }, 
        (p1, p2) -> p2.max > p1.max ? p2 : p1,
        p -> p.maxT
    ));
    // do something with maxT
}
Run Code Online (Sandbox Code Playgroud)

一个优点是,这创建了一个Pair在整个收集过程中使用的单个中间对象.每次接受元素时,都会使用新的最大值更新此持有者.修整器操作只返回最大元素并且忽略最大值.

  • 值得指出的是,如果你并行执行此操作,你将获得多个`Pair`对象的实例化. (3认同)
  • 您可能希望使用`if(result> = p.max)`而不是`if(result> p.max)`.否则你最终会在所有元素实际映射到`Integer.MIN_VALUE`的(不可否认的)事件中得到`null`.或者,您可以使用`Integer max = null;`和`if(p.max == null || result> p.max)`. (3认同)