简体   繁体   English

Lambda 表达式 vs 方法参考

[英]Lambda expression vs method reference

IntelliJ keeps proposing me to replace my lambda expressions with method references. IntelliJ 一直建议我用方法引用替换我的 lambda 表达式。

Is there any objective difference between both of them?两者有客观上的区别吗?

Let me offer some perspective on why we added this feature to the language, when clearly we didn't strictly need to (all methods refs can be expressed as lambdas.)让我提供一些关于为什么我们将这个特性添加到语言中的观点,显然我们并不严格需要(所有方法 refs 都可以表示为 lambdas。)

Note that there is no right answer .请注意,没有正确答案 Anyone who says "always use a method ref instead of a lambda" or "always use a lambda instead of a method ref" should be ignored.任何说“总是使用方法引用而不是 lambda”或“总是使用 lambda 而不是方法引用”的人都应该被忽略。

This question is very similar in spirit to "when should I use a named class vs an anonymous class"?这个问题在精神上与“我什么时候应该使用命名类与匿名类”非常相似? And the answer is the same: when you find it more readable .答案是一样的:当你发现它更具可读性时 There are certainly cases that are definitely one or definitely the other but there's a host of grey in the middle, and judgment must be used.肯定有肯定是一个或肯定是另一个的情况,但中间有很多灰色,必须使用判断。

The theory behind method refs is simple: names matter .方法 refs 背后的理论很简单:名称很重要 If a method has a name, then referring to it by name, rather than by an imperative bag of code that ultimately just turns around and invokes it, is often (but not always!) more clear and readable.如果一个方法有一个名字,那么通过名字来引用它,而不是通过最终只是调用它的命令式代码包,通常(但不总是!)更清晰易读。

The arguments about performance or about counting characters are mostly red herrings, and you should ignore them.关于性能或计数字符的争论大多是红鲱鱼,你应该忽略它们。 The goal is writing code that is crystal clear what it does.目标是编写清晰明确的代码。 Very often (but not always!) method refs win on this metric, so we included them as an option, to be used in those cases.很多时候(但不总是!)方法引用在这个指标上获胜,所以我们将它们作为一个选项包含在内,以用于这些情况。

A key consideration about whether method refs clarify or obfuscate intent is whether it is obvious from context what is the shape of the function being represented.关于方法引用是澄清还是混淆意图的一个关键考虑因素是从上下文中是否可以明显看出所表示的函数的形状是什么。 In some cases (eg, map(Person::getLastName) , it's quite clear from the context that a function that maps one thing to another is required, and in cases like this, method references shine. In others, using a method ref requires the reader to wonder about what kind of function is being described; this is a warning sign that a lambda might be more readable, even if it is longer.在某些情况下(例如, map(Person::getLastName) ,从上下文中可以清楚地看出需要一个将一件事映射到另一件事的函数,在这种情况下,方法引用大放异彩。在其他情况下,使用方法 ref 需要读者想知道正在描述什么样的函数;这是一个警告信号,表明 lambda 可能更具可读性,即使它更长。

Finally, what we've found is that most people at first steer away from method refs because they feel even newer and weirder than lambdas, and so initially find them "less readable", but over time, when they get used to the syntax, generally change their behavior and gravitate towards method references when they can.最后,我们发现大多数人一开始会避开方法引用,因为他们感觉比 lambda 更新颖、更奇怪,因此最初发现它们“可读性差”,但随着时间的推移,当他们习惯了语法时,通常会改变他们的行为并在可能的情况下倾向于方法引用。 So be aware that your own subjective initial "less readable" reaction almost certainly entails some aspect of familiarity bias, and you should give yourself a chance to get comfortable with both before rendering a stylistic opinion.因此请注意,您自己的主观初始“可读性较差”的反应几乎肯定会导致某些方面的熟悉偏见,并且您应该在呈现风格意见之前给自己一个机会来适应这两种情况。

Long lambda expressions consisting of several statements may reduce the readability of your code.由多个语句组成的长 lambda 表达式可能会降低代码的可读性 In such a case, extracting those statements in a method and referencing it may be a better choice.在这种情况下,在方法中提取这些语句并引用它可能是更好的选择。

The other reason may be re-usability .另一个原因可能是可重用性 Instead of copy&pasting your lambda expression of few statements, you can construct a method and call it from different places of your code.您可以构造一个方法并从代码的不同位置调用它,而不是复制和粘贴几个语句的 lambda 表达式。

As user stuchl4n3k wrote in comments to question there may exception occurs .正如用户 stuchl4n3k 在评论中所写的那样,可能会发生异常

Lets consider that some variable field is uninitialized field, then:让我们考虑一些变量field是未初始化的字段,然后:

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

will not crash, while不会崩溃,而

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

will crash with java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' , at a method reference statement line, at least on Android.将崩溃java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' ,在方法引用语句行,至少在 Android 上。

Todays IntelliJ notes "may change semantics" while suggesting this refactoring.今天的 IntelliJ 指出“可能会改变语义”,同时建议进行这种重构。

This happens when do "referencing" of instance method of a particular object.当对特定对象的实例方法进行“引用”时会发生这种情况。 Why?为什么? Lets check first two paragraphs of 15.13.3.让我们检查15.13.3 的前两段 Run-Time Evaluation of Method References : 方法参考的运行时评估

At run time, evaluation of a method reference expression is similar to evaluation of a class instance creation expression, insofar as normal completion produces a reference to an object.在运行时,方法引用表达式的求值类似于类实例创建表达式的求值,因为正常完成会生成对对象的引用。 Evaluation of a method reference expression is distinct from invocation of the method itself.方法引用表达式的评估不同于方法本身的调用。

First, if the method reference expression begins with an ExpressionName or a Primary , this subexpression is evaluated.首先,如果方法引用表达式以ExpressionNamePrimary开头,则计算此子表达式。 If the subexpression evaluates to null, a NullPointerException is raised , and the method reference expression completes abruptly.如果子表达式的计算结果为 null,则会引发NullPointerException ,并且方法引用表达式会突然完成。 If the subexpression completes abruptly, the method reference expression completes abruptly for the same reason.如果子表达式突然完成,方法引用表达式也会出于同样的原因突然完成。

In case of lambda expression, I'm unsure, final type is derived in compile-time from method declaration.在 lambda 表达式的情况下,我不确定最终类型是在编译时从方法声明派生的。 This is just simplification of what is going exactly.这只是对正在发生的事情的简化。 But lets assume that method runThisLater has been declared as eg void runThisLater(SamType obj) , where SamType is some Functional interface then runThisLater(()->field.method());但是让我们假设方法runThisLater已经被声明为例如void runThisLater(SamType obj) ,其中 SamType 是一些功能接口然后runThisLater(()->field.method()); translates into something like:翻译成类似的东西:

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

Additional info:附加信息:

While it is true that all methods references can be expressed as lambdas, there is a potential difference in semantics when side effects are involved.虽然所有方法引用都可以表示为 lambda,但当涉及副作用时,语义上存在潜在差异。 @areacode's example throwing an NPE in one case but not in the other is very explicit regarding the involved side effect. @areacode在一种情况下抛出NPE而在另一种情况下不抛出 NPE 的示例对于所涉及的副作用非常明确。 However, there is a more subtle case you could run into when working with CompletableFuture :但是,在使用CompletableFuture时,您可能会遇到更微妙的情况:

Let's simulate a task that takes a while (2 seconds) to complete via the following helper function slow :让我们通过以下帮助程序 function slow模拟一个需要一段时间(2 秒)才能完成的任务:

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

Then然后

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

Effectively runs both async tasks in parallel allowing the future to complete after roughly 2 seconds.有效地并行运行两个异步任务,允许未来在大约 2 秒后完成。

On the other hand if we refactor the ::thenApply method reference into a lambda, both async tasks would run sequentially one after each other and the future only completes after about 4 seconds.另一方面,如果我们将::thenApply方法引用重构为 lambda,则两个异步任务将依次运行,而 future 仅在大约 4 秒后完成。

Side note : while the example seems contrived, it does come up when you try to regain the applicative instance hidden in the future .旁注:虽然这个例子看起来是人为的,但当你试图重新获得隐藏在未来的应用实例时,它确实会出现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM