IntelliJ 一直建议我用方法引用替换 lambda 表达式。
他们之间有什么客观区别吗?
由多个语句组成的长 lambda 表达式可能会减少代码的 可读性。在这种情况下,在方法中提取这些语句并引用它可能是更好的选择。
另一个原因可能是 可重用性。您可以构造一个方法并从代码的不同位置调用它,而不是复制和粘贴少量语句的 lambda 表达式。
让我提供一些关于为什么我们要在语言中添加这个特性的观点,显然我们并不严格需要这样做(所有的方法参考都可以表示为 lambdas)
请注意,这里有 没有正确答案。任何说“始终使用方法 ref 而不是 lambda”或者“始终使用 lambda 而不是方法 ref”的人都应该被忽略。
这个问题在精神上与“何时应该使用命名类与匿名类”非常相似?答案是一样的: 当你觉得它更可读的时候。当然,有些案件肯定是一个或者肯定是另一个,但是中间有很多灰色地带,必须做出判断。
方法参考背后的理论很简单: 名字很重要。如果一个方法有一个名字,那么通过名字来引用它,而不是通过一个命令式的代码包来引用它,而这个代码包最终只是反过来调用它,这种情况经常发生(但并不总是如此!)更清晰易读。
关于性能或计数字符的争论大多是转移注意力,您应该忽略它们。我们的目标是编写清晰的代码。经常(但不总是!)方法参考赢在这个度量,所以我们包括他们作为一个选项,在这些情况下使用。
关于方法引用是澄清还是模糊意图的一个关键考虑是,从上下文来看,被表示的函数的形状是否明显。在某些情况下(例如,map(Person::getLastName)) ,从上下文中可以很清楚地看出,需要一个将一个事物映射到另一个事物的函数,在这种情况下,方法引用会发光。在其他情况下,使用方法 ref 需要读者想知道所描述的函数是什么类型; 这是一个警告信号,表明 lambda 可能更易读,即使它更长。
map(Person::getLastName)
最后,我们发现大多数人不使用方法引用,因为他们觉得它们比 lambdas 更新更奇怪,所以最初发现它们“不太可读”,但是随着时间的推移,当他们习惯了语法,通常会改变他们的行为,尽可能地使用方法引用。因此,要意识到,你自己主观的最初“不太可读”的反应几乎肯定包含熟悉性偏见的某些方面,你应该给自己一个机会,让两者都感到舒适,然后再提出一个风格上的意见。
用户 stuchl4n3k 在问题 可能会有例外发生的评论中写道。
假设某个变量 field是未初始化的字段,那么:
field
field = null; runThisLater(()->field.method()); field = new SomeObject();
不会崩溃,而
field = null; runThisLater(field::method); field = new SomeObject();
将与 NullPointerException: 尝试调用虚方法‘ java.lang. Class java.lang. Object.getClass ()’一起崩溃,在方法引用语句行,至少在 Android 上是这样。
今天 IntelliJ 注意到“可能会改变语义”,同时建议进行这种重构。
当“引用”特定对象的实例方法时会发生这种情况。为什么? 让我们检查一下。的前两段 15.13.3方法参考的运行时评估 :
在运行时,方法引用表达式的计算类似于 类实例创建表达式,只要正常完成生成引用 方法引用表达式的计算不同于调用 方法本身。 首先,如果方法引用表达式以 ExpressionName或 如果子表达式的计算结果为 null,则 a 引发 NullPointerException ,方法引用表达式完成 如果子表达式突然完成,则方法引用表达式 因为同样的原因突然结束。
在运行时,方法引用表达式的计算类似于 类实例创建表达式,只要正常完成生成引用 方法引用表达式的计算不同于调用 方法本身。
首先,如果方法引用表达式以 ExpressionName或 如果子表达式的计算结果为 null,则 a 引发 NullPointerException ,方法引用表达式完成 如果子表达式突然完成,则方法引用表达式 因为同样的原因突然结束。
NullPointerException
对于 lambda 表达式,我不确定,final 类型是在编译时从方法声明派生的。这只是对事情的简化。但是让我们假设方法 runThisLater已经声明为。 其中 SamType 是一些 功能界面,然后 runThisLater(()->field.method());翻译成类似这样的内容:
runThisLater
runThisLater(()->field.method());
runThisLater(new SamType() { void doSomething() { field.method(); } });
附加信息:
虽然所有的方法引用都可以表示为 lambdas,但是当涉及到副作用时,在语义上存在潜在的差异。在一种情况下抛出 @ areacode’s而在另一种情况下不抛出 NPE的例子对于所涉及的副作用是非常明确的。然而,在使用 CompletableFuture时,可能会遇到一个更微妙的情况:
NPE
CompletableFuture
让我们通过以下助手函数 slow来模拟一个需要一段时间(2秒)才能完成的任务:
slow
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,那么这两个异步任务将依次相继运行,并且将来只会在大约4秒后完成。
::thenApply
边注 : 虽然这个示例看起来有些做作,但是当您尝试 重新获得隐藏在未来的应用实例时,它确实出现了。