Lambda Expressions and Functional Interfaces: Tips and Best Practices
转载自https://www.baeldung.com/java-8-lambda-expressions-tips
1. Overview
Now that Java 8 has reached wide usage, patterns, and best practices have begun to emerge for some of its headlining features. In this tutorial, we will take a closer look to functional interfaces and lambda expressions.
Further reading:
Why Do Local Variables Used in Lambdas Have to Be Final or Effectively Final?
Java 8 – Powerful Comparison with Lambdas
2. Prefer Standard Functional Interfaces
Functional interfaces, which are gathered in the java.util.function package, satisfy most developers' needs in providing target types for lambda expressions and method references. Each of these interfaces is general and abstract, making them easy to adapt to almost any lambda expression. Developers should explore this package before creating new functional interfaces.
Consider an interface Foo:
|
1
2
3
4
|
@FunctionalInterfacepublic interface Foo { String method(String string);} |
and a method add() in some class UseFoo, which takes this interface as a parameter:
|
1
2
3
|
public String add(String string, Foo foo) { return foo.method(string);} |
To execute it, you would write:
|
1
2
|
Foo foo = parameter -> parameter + " from lambda";String result = useFoo.add("Message ", foo); |
Look closer and you will see that Foo is nothing more than a function that accepts one argument and produces a result. Java 8 already provides such an interface in Function<T,R> from the java.util.function package.
Now we can remove interface Foo completely and change our code to:
|
1
2
3
|
public String add(String string, Function<String, String> fn) { return fn.apply(string);} |
To execute this, we can write:
|
1
2
3
|
Function<String, String> fn = parameter -> parameter + " from lambda";String result = useFoo.add("Message ", fn); |
3. Use the @FunctionalInterface Annotation
Annotate your functional interfaces with @FunctionalInterface. At first, this annotation seems to be useless. Even without it, your interface will be treated as functional as long as it has just one abstract method.
But imagine a big project with several interfaces – it's hard to control everything manually. An interface, which was designed to be functional, could accidentally be changed by adding of other abstract method/methods, rendering it unusable as a functional interface.
But using the @FunctionalInterface annotation, the compiler will trigger an error in response to any attempt to break the predefined structure of a functional interface. It is also a very handy tool to make your application architecture easier to understand for other developers.
So, use this:
|
1
2
3
4
|
@FunctionalInterfacepublic interface Foo { String method();} |
instead of just:
|
1
2
3
|
public interface Foo { String method();} |
4. Don't Overuse Default Methods in Functional Interfaces
You can easily add default methods to the functional interface. This is acceptable to the functional interface contract as long as there is only one abstract method declaration:
|
1
2
3
4
5
|
@FunctionalInterfacepublic interface Foo { String method(); default void defaultMethod() {}} |
Functional interfaces can be extended by other functional interfaces if their abstract methods have the same signature. For example:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@FunctionalInterfacepublic interface FooExtended extends Baz, Bar {} @FunctionalInterfacepublic interface Baz { String method(); default void defaultBaz() {} } @FunctionalInterfacepublic interface Bar { String method(); default void defaultBar() {} } |
Just as with regular interfaces, extending different functional interfaces with the same default method can be problematic. For example, assume that interfaces Bar and Baz both have a default method defaultCommon(). In this case, you will get a compile-time error:
|
1
|
interface Foo inherits unrelated defaults for defaultCommon() from types Baz and Bar... |
To fix this, defaultCommon() method should be overridden in the Foo interface. You can, of course, provide a custom implementation of this method. But if you want to use one of the parent interfaces' implementations (for example, from the Baz interface), add following line of code to the defaultCommon() method's body:
|
1
|
Baz.super.defaultCommon(); |
But be careful. Adding too many default methods to the interface is not a very good architectural decision. It is should be viewed as a compromise, only to be used when required, for upgrading existing interfaces without breaking backward compatibility.
5. Instantiate Functional Interfaces with Lambda Expressions
The compiler will allow you to use an inner class to instantiate a functional interface. However, this can lead to very verbose code. You should prefer lambda expressions:
|
1
|
Foo foo = parameter -> parameter + " from Foo"; |
over an inner class:
|
1
2
3
4
5
6
|
Foo fooByIC = new Foo() { @Override public String method(String string) { return string + " from Foo"; }}; |
The lambda expression approach can be used for any suitable interface from old libraries. It is usable for interfaces like Runnable, Comparator, and so on. However, this doesn't mean that you should review your whole older codebase and change everything.
6. Avoid Overloading Methods with Functional Interfaces as Parameters
Use methods with different names to avoid collisions; let's look at an example:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public interface Processor { String process(Callable<String> c) throws Exception; String process(Supplier<String> s);}public class ProcessorImpl implements Processor { @Override public String process(Callable<String> c) throws Exception { // implementation details } @Override public String process(Supplier<String> s) { // implementation details }} |
At first glance, this seems reasonable. But any attempt to execute either of the ProcessorImpl‘s methods:
|
1
|
String result = processor.process(() -> "abc"); |
ends with an error with the following message:
|
1
2
3
4
5
|
reference to process is ambiguousboth method process(java.util.concurrent.Callable<java.lang.String>) in com.baeldung.java8.lambda.tips.ProcessorImpl and method process(java.util.function.Supplier<java.lang.String>) in com.baeldung.java8.lambda.tips.ProcessorImpl match |
To solve this problem, we have two options. The first is to use methods with different names:
|
1
2
3
|
String processWithCallable(Callable<String> c) throws Exception;String processWithSupplier(Supplier<String> s); |
The second is to perform casting manually. This is not preferred.
|
1
|
String result = processor.process((Supplier<String>) () -> "abc"); |
7. Don’t Treat Lambda Expressions as Inner Classes
Despite our previous example, where we essentially substituted inner class by a lambda expression, the two concepts are different in an important way: scope.
When you use an inner class, it creates a new scope. You can hide local variables from the enclosing scope by instantiating new local variables with the same names. You can also use the keyword this inside your inner class as a reference to its instance.
However, lambda expressions work with enclosing scope. You can’t hide variables from the enclosing scope inside the lambda’s body. In this case, the keyword this is a reference to an enclosing instance.
For example, in the class UseFoo you have an instance variable value:
|
1
|
private String value = "Enclosing scope value"; |
Then in some method of this class place the following code and execute this method.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public String scopeExperiment() { Foo fooIC = new Foo() { String value = "Inner class value"; @Override public String method(String string) { return this.value; } }; String resultIC = fooIC.method(""); Foo fooLambda = parameter -> { String value = "Lambda value"; return this.value; }; String resultLambda = fooLambda.method(""); return "Results: resultIC = " + resultIC + ", resultLambda = " + resultLambda;} |
If you execute the scopeExperiment() method, you will get the following result: Results: resultIC = Inner class value, resultLambda = Enclosing scope value
As you can see, by calling this.value in IC, you can access a local variable from its instance. But in the case of the lambda, this.value call gives you access to the variable value which is defined in the UseFoo class, but not to the variable value defined inside the lambda's body.
8. Keep Lambda Expressions Short And Self-explanatory
If possible, use one line constructions instead of a large block of code. Remember lambdas should be an expression, not a narrative. Despite its concise syntax, lambdas should precisely express the functionality they provide.
This is mainly stylistic advice, as performance will not change drastically. In general, however, it is much easier to understand and to work with such code.
This can be achieved in many ways – let's have a closer look.
8.1. Avoid Blocks of Code in Lambda's Body
In an ideal situation, lambdas should be written in one line of code. With this approach, the lambda is a self-explanatory construction, which declares what action should be executed with what data (in the case of lambdas with parameters).
If you have a large block of code, the lambda's functionality is not immediately clear.
With this in mind, do the following:
|
1
|
Foo foo = parameter -> buildString(parameter); |
|
1
2
3
4
5
|
private String buildString(String parameter) { String result = "Something " + parameter; //many lines of code return result;} |
instead of:
|
1
2
3
4
|
Foo foo = parameter -> { String result = "Something " + parameter; //many lines of code return result; }; |
However, please don't use this “one-line lambda” rule as dogma. If you have two or three lines in lambda's definition, it may not be valuable to extract that code into another method.
8.2. Avoid Specifying Parameter Types
A compiler in most cases is able to resolve the type of lambda parameters with the help of type inference. Therefore, adding a type to the parameters is optional and can be omitted.
Do this:
|
1
|
(a, b) -> a.toLowerCase() + b.toLowerCase(); |
instead of this:
|
1
|
(String a, String b) -> a.toLowerCase() + b.toLowerCase(); |
8.3. Avoid Parentheses Around a Single Parameter
Lambda syntax requires parentheses only around more than one parameter or when there is no parameter at all. That is why it is safe to make your code a little bit shorter and to exclude parentheses when there is only one parameter.
So, do this:
|
1
|
a -> a.toLowerCase(); |
instead of this:
|
1
|
(a) -> a.toLowerCase(); |
8.4. Avoid Return Statement and Braces
Braces and return statements are optional in one-line lambda bodies. This means, that they can be omitted for clarity and conciseness.
Do this:
|
1
|
a -> a.toLowerCase(); |
instead of this:
|
1
|
a -> {return a.toLowerCase()}; |
8.5. Use Method References
Very often, even in our previous examples, lambda expressions just call methods which are already implemented elsewhere. In this situation, it is very useful to use another Java 8 feature: method references.
So, the lambda expression:
|
1
|
a -> a.toLowerCase(); |
could be substituted by:
|
1
|
String::toLowerCase; |
This is not always shorter, but it makes the code more readable.
9. Use “Effectively Final” Variables
Accessing a non-final variable inside lambda expressions will cause the compile-time error. But it doesn’t mean that you should mark every target variable as final.
According to the “effectively final” concept, a compiler treats every variable as final, as long as it is assigned only once.
It is safe to use such variables inside lambdas because the compiler will control their state and trigger a compile-time error immediately after any attempt to change them.
For example, the following code will not compile:
|
1
2
3
4
5
6
7
|
public void method() { String localVariable = "Local"; Foo foo = parameter -> { String localVariable = parameter; return localVariable; };} |
The compiler will inform you that:
|
1
|
Variable 'localVariable' is already defined in the scope. |
This approach should simplify the process of making lambda execution thread-safe.
10. Protect Object Variables from Mutation
One of the main purposes of lambdas is use in parallel computing – which means that they're really helpful when it comes to thread-safety.
The “effectively final” paradigm helps a lot here, but not in every case. Lambdas can't change a value of an object from enclosing scope. But in the case of mutable object variables, a state could be changed inside lambda expressions.
Consider the following code:
|
1
2
3
|
int[] total = new int[1];Runnable r = () -> total[0]++;r.run(); |
This code is legal, as total variable remains “effectively final”. But will the object it references to have the same state after execution of the lambda? No!
Keep this example as a reminder to avoid code that can cause unexpected mutations.
11. Conclusion
In this tutorial, we saw some best practices and pitfalls in Java 8's lambda expressions and functional interfaces. Despite the utility and power of these new features, they are just tools. Every developer should pay attention while using them.
The complete source code for the example is available in this GitHub project – this is a Maven and Eclipse project, so it can be imported and used as-is.
Lambda Expressions and Functional Interfaces: Tips and Best Practices的更多相关文章
- Functional Programming without Lambda - Part 1 Functional Composition
Functions in Java Prior to the introduction of Lambda Expressions feature in version 8, Java had lon ...
- jdk8新特性之lambda expressions
本文分两部分: 语法简单说明 lambda的使用 注:这两部分内容均以类+注释的方式进行说明,并且内容均来自官方教程(https://docs.oracle.com/javase/tutorial/j ...
- JAVA 8 Lambda表达式-Lambda Expressions
Lambda表达式介绍 Lambda表达式是在java规范提案JSR 335中定义的,Java 8 中引入了Lambda表达式,并被认为是Java 8最大的新特性,Lambda表达式促进了函数式编程, ...
- java语言中的匿名类与lambda表达式介绍与总结 (Anonymous Classes and Lambda Expressions)
2017/6/30 转载写明出处:http://www.cnblogs.com/daren-lin/p/anonymous-classes-and-lambda-expressions-in-java ...
- 1.8 新特性之 Lambda Expressions
Lambda expressions are allowed only at source level 1.8 or above The target type of this expression ...
- Hacking Lambda Expressions in Java
Hacking Lambda Expressions in Javahttps://dzone.com/articles/hacking-lambda-expressions-in-java At t ...
- C# Lambda Expressions 简介
C# Lambda Expressions 简介 原文http://msdn2.microsoft.com/en-us/library/bb397687.aspx 翻译:朱之光 (larry1zhu@ ...
- Starter Set of Functional Interfaces
Java Development Kit 8 has a number of functional interfaces. Here we review the starter set-the int ...
- lambda expressions are not supported at this language level
IDEA下报错:lambda expressions are not supported at this language level 解决: 1. File -> Project Struct ...
随机推荐
- [刘阳Java]_EasyUI环境搭建_第2讲
在EasyUI的第1讲中我们介绍了学习EasyUI能够做什么,这次我们得快速搭建一个EasyUI环境,来测试一下它的运行效果 1.jQuery EasyUI环境搭建 <script type=& ...
- 【重构】Bilibili UWP 客户端下载视频文件重命名 2.0
代码已上传Github:https://github.com/zsy0216/BatchModifyBilibiliName 较 master 分支的改变: - 优化了重命名的代码,覆盖更全面,更准确 ...
- etcd raft 处理流程图系列1-raftexample
最近在看raft相关的代码和实现,发现etcd的raft模块在实现上还是比较灵活的,但缺点就是需要用户实现比较多的功能,如存储和网络等,同时带来的优点就是不会对用户的存储和传输作限制.网上对该模块的描 ...
- Java预科:DOS命令及电脑快捷键
Java预科:DOS命令及电脑快捷键 1.快捷键 常用快捷键 a/t +f4关闭窗口 cmd窗口打开快捷方式 1.开始+系统+命令提示符 2.win+r 输入cmd 3.在任意文件夹下面,按住shif ...
- 【NLP学习其四】如何构建自己用于训练的数据集?什么是词性标注?
数据集与词性标注 数据集是NLP中的重要一环. 但是提到数据集,很多人的第一个想法可能是:"这玩意从网上下载就好了,不用管". 真的不用管?最开始我也是这么认为的 于是我直奔CoN ...
- linux中的防火墙netfilter iptables
目录 一.Linux防火墙基础 1.1 ptables的表.链结构 1.2 数据包控制的匹配流程 二.编写防火墙规则 1.iptables的安装 2.1 基本语法.控制类型 一般在生产环境中设置网络型 ...
- RHCAS_DAY06
vi/vim文本编辑器 Vim是从 vi 发展出来的一个文本编辑器,vim 具有程序编辑的能力,可以主动的以字体颜色辨别语法的正确性 vi/vim 共分为三种模式:命令模式.输入模式.底线命令模式(末 ...
- RHCSA_DAY04
软连接与硬连接 Linux中的链接文件类似于windows中的快捷方式 软连接特点:软连接可以跨分区,可以对目录进行链接,源文件删除后,链接文件不可用 软连接命令格式:ln -s 源文件路径 目标路 ...
- 35岁Android程序员被阿里辞退,生活压力太大痛哭,中年危机如何自救?
多数人都喜欢安逸的生活,尤其是随着年龄的增长,很多人都希望工作和生活趋于稳定,不愿意再让生活有很大的变动.可是,当达到一定的年龄时,危机还是存在的. 之前有一位阿里员工在脉脉上,晒出了自己被辞退的经历 ...
- 天梯赛 L1-058 6翻了
传送门:https://pintia.cn/problem-sets/994805046380707840/problems/1111914599408664577 这道字符串题,只是天梯赛L1的题, ...