首页 > 解决方案 > 对静态方法或返回 lambda 的静态方法的方法引用

问题描述

在开发过程中,我总是不得不一遍又一遍地重写相同的 lambda 表达式,这是非常多余的,而且在大多数情况下,我公司强加的代码格式化策略也无济于事。因此,我将这些常见的 lambda作为静态方法移到了实用程序类中,并将它们用作方法引用。我拥有的最好的例子是与 java.util.stream.Collectors.toMap(Function, Function, BinaryOperator, Supplier) 结合使用的 Throwing 合并。总是必须写 (a,b) -> {throw new IllegalArgumentException("Some message");}; 仅仅因为我想使用自定义地图实现很麻烦。

//First Form

public static <E> E throwingMerger(E k1, E k2) {
    throw new IllegalArgumentException("Duplicate key " + k1 + " not allowed!");
  }

//Given a list of Car objects with proper getters
Map<String,Car> numberPlateToCar=cars.stream()//
   .collect(toMap(Car::getNumberPlate,identity(),StreamUtils::throwingMerger,LinkedHasMap::new))
//Second Form 

  public static <E> BinaryOperator<E> throwingMerger() {
    return (k1, k2) -> {
      throw new IllegalArgumentException("Duplicate key " + k1 + " not allowed!");
    };
  }
Map<String,Car> numberPlateToCar=cars.stream()//
   .collect(toMap(Car::getNumberPlate,identity(),StreamUtils.throwingMerger(),LinkedHasMap::new))

我的问题如下:

标签: javajava-8

解决方案


两种变体都不比另一种更正确。

此外,没有显着的性能差异,因为相关的字节码甚至是相同的。在任何一种情况下,都会有一个方法在您的类中保存一个 throw 语句,以及一个运行时生成的类的实例,它将调用该方法。

请注意,您可以在 JDK 本身中找到这两种模式。

  • Function.identity()并且Map.Entry.comparingByKey()是包含 lambda 表达式的工厂方法的示例
  • Double::sum, Objects::isNull, 或Objects::nonNull是对目标方法的方法引用示例

一般来说,如果还有直接调用方法的用例,最好将它们作为 API 方法提供,也可以通过方法引用来引用,例如Integer::compareObjects::requireNonNullMath::max

另一方面,提供工厂方法使方法引用成为实现细节,当有理由这样做时,您可以更改它。例如,您是否知道它Comparator.naturalOrder()没有实现T::compareTo?大多数时候,你不需要知道。

当然,带附加参数的工厂方法根本不能用方法引用代替;有时,您希望类的无参数方法与带参数的方法对称。


内存消耗只有很小的差异。给定当前的实现,例如,每次出现Objects::isNull都会导致创建一个运行时类和一个实例,然后它们将被重用于特定的代码位置。相比之下,其中的实现Function.identity()只产生一个代码位置,因此,一个运行时类和实例。另请参阅此答案

但必须强调的是,这是特定于特定实现的,因为该策略是由 JRE 实现的,此外,我们谈论的是有限的、相当少量的代码位置和对象。


顺便说一句,这些方法并不矛盾。你甚至可以同时拥有:

// for calling directly
public static <E> E alwaysThrow(E k1, E k2) {
    // by the way, k1 is not the key, see https://stackoverflow.com/a/45210944/2711488
    throw new IllegalArgumentException("Duplicate key " + k1 + " not allowed!");
}
// when needing a shared BinaryOperator
public static <E> BinaryOperator<E> throwingMerger() {
    return ContainingClass::alwaysThrow;
}

请注意,还有一点需要考虑;工厂方法总是返回特定接口的物化实例,即BinaryOperator. 对于需要绑定到不同接口的方法,根据上下文,无论如何都需要这些地方的方法引用。这就是为什么你可以写

DoubleBinaryOperator sum1 = Double::sum;
BinaryOperator<Double> sum2 = Double::sum;
BiFunction<Integer,Integer,Double> sum3 = Double::sum;

如果只有工厂方法返回 a ,这是不可能的DoubleBinaryOperator


推荐阅读