在流操作中使用方法引用和函数对象之间的区别?
Differences between using a method reference and function object in stream operations?
当使用 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 表达式。因此,我想从那里重构它。我看到两个选项,要么在 class:
中创建 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);
除了明显的品味问题,这两种方法有什么优点或缺点吗?
例如,我知道堆栈跟踪在方法参考案例中变得更具可读性,这是一个小优势。
除非有一些我不知道的额外魔法,否则当前的 lambda 实现会将您的非捕获 lambda 脱糖为静态方法,并将缓存 lambda 实例。通过显式地做同样的事情(对 lambda 的 static final
引用),你基本上是在重复隐式工作,所以你最终得到了对同一事物的两个缓存引用。您还击败了 lambda 实例的惰性初始化,否则您将免费获得它。
这就是为什么我更喜欢方法引用:它更容易编写,更惯用,而且在实现方面似乎更轻量级。
将多行 lambda 表达式重构为普通方法并在流中使用方法引用的一些原因是可维护性和可测试性。
当非平凡的映射器函数变成普通方法时,它获得了一个名称,并且可以用于单元测试框架。您可以轻松编写直接调用它的测试,必要时可以将其存根。
该方法还可以有与之关联的文档注释,包括参数和 return 值的文档。
如果映射器函数比较复杂,这两个都非常有用。
将 lambda 表达式分配给一个字段并没有错,但我认为这样做的主要原因是如果该字段在运行时被修改,例如,当应用程序的状态发生变化时。即使在这些情况下,我可能会考虑编写普通方法,然后使用方法引用而不是多行 lambda 表达式来分配字段。
使用字段的一个缺点是它需要您显式声明功能接口的泛型类型参数。 IDE 可以帮助解决这个问题,但它会使程序变得混乱。
我猜测带有方法引用的普通方法总是比使用用 lambda 表达式初始化的 final 字段更可取。
当使用 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 表达式。因此,我想从那里重构它。我看到两个选项,要么在 class:
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);
除了明显的品味问题,这两种方法有什么优点或缺点吗?
例如,我知道堆栈跟踪在方法参考案例中变得更具可读性,这是一个小优势。
除非有一些我不知道的额外魔法,否则当前的 lambda 实现会将您的非捕获 lambda 脱糖为静态方法,并将缓存 lambda 实例。通过显式地做同样的事情(对 lambda 的 static final
引用),你基本上是在重复隐式工作,所以你最终得到了对同一事物的两个缓存引用。您还击败了 lambda 实例的惰性初始化,否则您将免费获得它。
这就是为什么我更喜欢方法引用:它更容易编写,更惯用,而且在实现方面似乎更轻量级。
将多行 lambda 表达式重构为普通方法并在流中使用方法引用的一些原因是可维护性和可测试性。
当非平凡的映射器函数变成普通方法时,它获得了一个名称,并且可以用于单元测试框架。您可以轻松编写直接调用它的测试,必要时可以将其存根。
该方法还可以有与之关联的文档注释,包括参数和 return 值的文档。
如果映射器函数比较复杂,这两个都非常有用。
将 lambda 表达式分配给一个字段并没有错,但我认为这样做的主要原因是如果该字段在运行时被修改,例如,当应用程序的状态发生变化时。即使在这些情况下,我可能会考虑编写普通方法,然后使用方法引用而不是多行 lambda 表达式来分配字段。
使用字段的一个缺点是它需要您显式声明功能接口的泛型类型参数。 IDE 可以帮助解决这个问题,但它会使程序变得混乱。
我猜测带有方法引用的普通方法总是比使用用 lambda 表达式初始化的 final 字段更可取。