3

在使用 Java 8 流时,我经常发现我需要重构一个多语句 lambda 表达式。我将用一个简单的例子来说明这一点。假设我已经开始编写这段代码:

Stream.of(1, 3).map(i -> {
    if (i == 1) {
        return "I";
    } else if (i == 3) {
        return "E";
    }
    return "";
}).forEach(System.out::println);

现在我不太喜欢map调用中的大 lambda 表达式。因此,我想从那里重构它。Function我看到两个选项,或者我在我的班级中创建一个实例:

private static Function<Integer, String> mapper = i -> {
    if (i == 1) {
        return "I";
    } else if (i == 3) {
        return "E";
    }
    return "";
};

并像这样使用它:

Stream.of(1, 3).map(mapper).forEach(System.out::println);

或者我只是制作一个方法:

private static String map(Integer i) {
    if (i == 1) {
        return "I";
    } else if (i == 3) {
        return "E";
    }
    return "";
}

并使用方法参考:

Stream.of(1, 3).map(Test::map).forEach(System.out::println);

除了明显的口味问题之外,这两种方法有什么优点或缺点吗?

例如,我知道堆栈跟踪在方法参考案例中变得更具可读性,这是一个小优势。

4

2 回答 2

6

除非有一些我不知道的额外魔法,否则当前的 lambda 实现会将您的非捕获 lambda 脱糖为静态方法,并将缓存 lambda 实例。通过显式地执行相同的操作(static final对 lambda 的引用),您基本上是在复制该隐式工作,因此您最终会得到对同一事物的两个缓存引用。您还击败了 lambda 实例的延迟初始化,否则您将免费获得。

这就是为什么我更喜欢方法引用的原因:它更容易编写,更惯用,而且在实现方面似乎也更轻量级。

于 2015-08-31T11:47:24.550 回答
5

将多行 lambda 表达式重构为普通方法以及在流中使用方法引用的一些原因是可维护性和可测试性。

当非平凡的映射器函数转换为普通方法时,它会获得一个名称,并且它可以用于单元测试框架。您可以轻松编写直接调用它的测试,并且可以在必要时将其删除。

该方法还可以具有与之关联的文档注释,包括参数和返回值的文档。

如果映射器功能很复杂,这两种方法都非常有用。

将 lambda 表达式分配给字段并没有错,但我认为这样做的主要原因是如果该字段在运行时被修改,例如,当应用程序的状态发生变化时。即使在这些情况下,我也可能会考虑编写普通方法,然后使用方法引用而不是多行 lambda 表达式来分配字段。

使用字段的一个缺点是它需要您显式声明功能接口的泛型类型参数。IDE 可以帮助解决这个问题,但它会增加程序的混乱度。

我猜想带有方法引用的普通方法总是比使用用 lambda 表达式初始化的 final 字段更可取。

于 2015-08-31T15:23:34.430 回答