Intellij Idea Lambda表达式与方法引用

5rgfhyps  于 2023-04-05  发布在  其他
关注(0)|答案(4)|浏览(186)

IntelliJ一直建议我用方法引用替换lambda表达式。
这两者之间是否存在客观差异?

vcudknz3

vcudknz31#

让我来谈谈为什么我们在语言中添加了这个特性,显然我们并不严格需要(所有方法引用都可以表示为lambda)。
请注意,* 没有正确答案 。任何说“总是使用方法ref而不是lambda”或“总是使用lambda而不是方法ref”的人都应该被忽略。
这个问题在精神上与“什么时候应该使用命名类还是匿名类”非常相似?答案是一样的:
当你发现它更可读 *。当然有情况下,肯定是一个或肯定是另一个,但有一个灰色的主机在中间,和判断必须使用。
方法refs背后的理论很简单:如果一个方法有一个名字,那么通过名字来引用它,而不是通过一个命令式的代码包,最终只是反过来调用它,通常(但并不总是!)更清晰易读。
关于性能或计数字符的争论大多是转移注意力的,你应该忽略它们。目标是编写代码,清楚地知道它做了什么。通常(但并不总是!)方法引用在这个指标上获胜,所以我们将它们作为一个选项,在这些情况下使用。
关于方法引用是澄清还是混淆意图的一个关键考虑因素是,从上下文来看,所表示的函数的形状是否显而易见。在某些情况下(例如,map(Person::getLastName),从上下文来看,需要一个将一个事物Map到另一个事物的函数是非常清楚的,并且在这种情况下,方法引用闪耀。在其他情况下,使用方法ref要求读者想知道描述的是什么类型的函数;这是一个警告信号,说明lambda可能更易读,即使它更长。
最后,我们发现,大多数人 * 一开始 * 会避开方法引用,因为它们感觉比lambda表达式更新更奇怪,所以最初会发现它们“可读性较差”,但随着时间的推移,当他们习惯了语法时,通常会改变它们的行为,并在可能的时候倾向于方法引用。所以要注意,你自己的主观初始化“可读性较低”。这种React几乎肯定包含了熟悉性偏见的某些方面,你应该给予自己一个机会,让自己在表达风格观点之前对两者都感到舒服。

6psbrbz9

6psbrbz92#

由多个语句组成的长lambda表达式可能会降低代码的可读性。在这种情况下,在方法中提取这些语句并引用它可能是更好的选择。
另一个原因可能是可重用性。你可以构造一个方法,并从代码的不同位置调用它,而不是复制和粘贴几个语句的lambda表达式。

l7wslrjt

l7wslrjt3#

由于用户stuchl4n3k在评论中写道问题可能发生异常
假设某个变量fieldnull,则:

field = null;
runThisLater(()->field.method());
field = new SomeObject();

不会崩溃,而

field = null;
runThisLater(field::method);
field = new SomeObject();

将崩溃,并出现 java.lang.NullPointerException:尝试在方法引用语句行调用虚拟方法'java.lang.Class java.lang.Object.getClass()',至少在Android上。
今天的IntelliJ在建议这种重构时指出“可能会改变语义”。
当“引用”特定对象的示例方法时会发生这种情况。为什么?让我们检查15.13.3.方法引用的运行时评估的前两段:
在运行时,方法引用表达式的求值类似于类示例创建表达式的求值,因为正常完成会产生对对象的引用。方法引用表达式的求值与方法本身的调用不同。
首先,如果方法引用表达式以 ExpressionNamePrimary 开头,则计算此子表达式。如果子表达式计算为null,则引发NullPointerException,并且方法引用表达式突然完成。如果子表达式突然完成,则方法引用表达式出于相同的原因突然完成。
在lambda表达式的情况下,我不确定,final类型是在编译时从方法声明中派生出来的。这只是对实际情况的简化。但是让我们假设方法runThisLater已经被声明为例如void runThisLater(SamType obj),其中SamType是一些 *Functional接口 *,那么runThisLater(()->field.method());将转换为如下内容:

runThisLater(new SamType() {  
    void doSomething() { 
        field.method();
    }
});

其他信息:

wljmcqd8

wljmcqd84#

虽然所有的方法引用都可以用lambdas表示,但当涉及到副作用时,语义上存在潜在的差异。@areacode的例子在一种情况下抛出NPE,而在另一种情况下不抛出NPE,这对于所涉及的副作用非常明确。然而,在使用CompletableFuture时,您可能会遇到更微妙的情况:
让我们通过以下辅助函数slow模拟一个需要一段时间(2秒)才能完成的任务:

private static <T> Supplier<T> slow(T s) {
    return () -> {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {}
        return s;
    };
}

然后

var result =
    CompletableFuture.supplyAsync(slow(Function.identity()))
        .thenCompose(supplyAsync(slow("foo"))::thenApply);

有效地并行运行两个异步任务,允许未来在大约2秒后完成。
另一方面,如果我们将::thenApply方法引用重构为lambda,两个异步任务将依次运行,并且future仅在大约4秒后完成。

附注:虽然这个例子看起来很做作,但当您尝试regain the applicative instance hidden in the future时,它确实出现了。

相关问题