Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/348.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 在流操作中使用方法引用和函数对象之间的区别?_Java_Lambda_Java 8_Method Reference - Fatal编程技术网

Java 在流操作中使用方法引用和函数对象之间的区别?

Java 在流操作中使用方法引用和函数对象之间的区别?,java,lambda,java-8,method-reference,Java,Lambda,Java 8,Method Reference,使用Java8流时,我经常发现需要重构多语句lambda表达式。我将用一个简单的例子来说明这一点。假设我已经开始编写以下代码: Stream.of(1, 3).map(i -> { if (i == 1) { return "I"; } else if (i == 3) { return "E"; } return ""; }).forEach(System.out::println); 现在我不太喜欢map调用中的大lam

使用Java8流时,我经常发现需要重构多语句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表达式。因此,我想重构它。我看到两个选项,或者我在我的类中创建一个
函数的实例

private static Function<Integer, String> mapper = i -> {
    if (i == 1) {
        return "I";
    } else if (i == 3) {
        return "E";
    }
    return "";
};
或者我简单地做一个方法:

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的
静态final
引用),基本上是在复制隐式工作,因此最终会得到对相同对象的两个缓存引用。您还克服了lambda实例的延迟初始化,否则您将免费获得该实例


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

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

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

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

如果映射器函数很复杂,这两个函数都非常有用

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

使用字段的一个缺点是需要显式声明函数接口的泛型类型参数。IDE可以帮助实现这一点,但它会给程序增加混乱


< P>我想,使用方法引用的普通方法总是比使用lambda表达式初始化的最终字段更可取。

我也会考虑<代码> .map(i -> i=1)?i:i=3?“e”:“/”代码>当前IMPL将无论如何将您的lambda转换为私有方法。@ TayrValeEV在这个简单的例子中表示同意,但在更复杂的情况下,我经常遇到这种情况,外部方法是唯一的选择。我个人更喜欢方法引用,而不是返回
函数的方法,我认为前者更简洁、更“功能性”——后者在Java 8之前就已经出现了。我觉得方法引用可以整理成
invokeDynamic
,这可能会使它更有效-但我没有一个引用来支持这一点。我要补充的是
static final
将被初始化,即使您不调用包含lambda的特定方法。这实际上意味着,
LambdaMetafactory
将生成额外的匿名类,即使您从未使用过它。使用方法引用或就地lambda只有在实际需要时才会生成此类。好吧,您不会有两个
静态
字段,因为使用字段无法重用lambda实现实例,但除此之外,它是正确的,如中所述。我还想强调的是,在实现不缓存实例的情况下,它很可能有理由做出此决定,而手动缓存会抵消此决定。所以通常,让JRE来决定是最好的选择。@Holger你的意思是,你专门检查过这段代码吗?所以实际上有一种机制,我称之为“我不知道的额外魔法”?顺便说一句,原因可能正是缓存是手动完成的。顺便说一下,引导可能同时运行。它只保证以后最多使用一个结果,所以在并发生成的情况下,所有其他结果都会被丢弃。但是,在Oracle的实现中,工厂本身将显式锁定,以避免在并发情况下执行两次工作。结果是
CallSite
实例持有
MethodHandle
。在无状态lambda的情况下,它包含一个构造的lambda实例。否则,它将是一个指向lambda实现类的构造函数的句柄(或者该类的
静态
工厂方法,我不知道他们为什么决定这样做)。不管怎样,
invokedynamic
指令的后续执行行为类似于执行
MethodHandle
。我几乎要提到相反的内容:使用静态lambda字段的唯一好理由是显式类型参数——在您遇到类型推断问题的情况下。@MarkoTopolnik我想这是一个有效的用法对于静态lambda字段,但通常为了帮助类型推断,我使用类型见证或强制转换。是的,但是如果有很多使用站点必须使用相同的类型,那么对于全局变量来说就是这样。不过,这可能非常罕见。
Stream.of(1, 3).map(Test::map).forEach(System.out::println);