1.概述

本文主要深入研究java 8中的函数式接口和Lambda表达式,并介绍最佳实践。

2.使用标准的函数式接口

包java.util.function中的函数是接口已经可以满足大部分的java开发者了。这些函数式接口足够的通用和抽象,使用他们可以简单的接收大部分的lambda表达式。开发者应该在创建自己的函数式接口前了解清楚现有的。

考虑一个接口Foo:

1
2
3
4
public interface  {
String method(String string);
}

然后另外一个类UseFoo里面有一个用这个接口做参数的add()方法:

1
2
3
public String add(String string, Foo foo) {
return foo.method(string);
}

运行他可以这样写:

1
2
Foo foo = parameter -> parameter + " from lambda";
String result = useFoo.add("Message ", foo);

进一步的观察你会发现Foo接口只有一个方法,并这个这个方法接收一个参数和返回一个结果。其实Java 8 已经提供了这样一个接口Function<T,R>

我们用Function<T,R>来写的话,可以这样:

1
2
3
public String add(String string, Function<String, String> fn) {
return fn.apply(string);
}

运行他可以这样写

1
2
Function<String, String> fn = parameter -> parameter + " from lambda";
String result = useFoo.add("Message ", fn);

3.使用 @FunctionalInterface 注解

在你的函数式接口上用 @FunctionalInterface 。这个接口看起来是没有作用的,甚至你不加上他你的函数式接口还是能工作。

但是你想象一下,一个大工程中可能有很多个接口,其中有一个你打算作为的函数式接口,但是意外的在这个接口中添加了另一个别的抽象方法。这会导致之前用到这个接口的地方失效。

但是你用了 @FunctionalInterface 这个注解后,如果你破坏了预定义的结构编译器会触发一个错误.

所以,用这样的代码

1
2
3
4
public interface  {
String method();
}

来代替

1
2
3
public interface  {
String method();
}

4.不要在函数式接口中过度使用默认方法

函数式接口中使用默认方法是可以的:

1
2
3
4
5
public interface  {
String method();
default void defaultMethod() {}
}

函数式接口也可以继承函数式接口,只要他们的抽象方法拥有同样的标识(signature).例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface FooExtended extends Baz, Bar {}

public interface Baz {  
String method();
default void defaultBaz() {}
} @FunctionalInterface
public interface Bar {
String method();
default void defaultBar() {}
}

但是像所有的普通接口一样,扩展不同的函数式接口也会有默认方法冲突的问题。例如BarBaz有相同的默认方法defaultCommon(),然后你就会得到一个编译错误

1
interface Foo inherits unrelated defaults for defaultCommon() from types Baz and Bar...

为了解决这个问题,deaultCommon()方法应该在Foo接口中覆盖。当然,你可以自定义方法的实现就。但是如果你想用任意一个父接口(例如Baz)的默认方法,你需要这样写

1
Baz.super.defaultCommon();

添加太多的默认方法不是一个好的设计方案。为了能够保证接口的向后兼容性,你应该在只有必要的时候才使用默认方法。

5.使用Lambda表达式来实例化函数试接口

编译器允许你使用内部类来实例化函数式接口,但是这样会让你的代码变得冗长。所以用lambda表达式更好:

1
Foo foo = parameter -> parameter + " from Foo";

用内部类:

1
2
3
4
5
6
Foo fooByIC = new Foo() {
@Override
public String method(String string) {
return string + " from Foo";
}
};

Lambda表达式可以适用于很多旧库的接口,但是这不意味着适用于全部接口。

6.避免使用函数式接口作为重载函数的参数

使用不同的方法名来避免冲突,看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface Adder {
String add(Function<String, String> f);
void add(Consumer<Integer> f);
} public class AdderImpl implements Adder { @Override
public String 大专栏  Lambda表达式和函数试接口的最佳实践 · LiangYongrui's Studio add(Function<String, String> f) {
return f.apply("Something ");
} @Override
public void add(Consumer<Integer> f) {}
}

但是当你尝试运行任何AddrImpl的方法时:

1
String r = adderImpl.add(a -> a + " from lambda");

你将会得到错误信息:

1
2
3
4
5
reference to add is ambiguous both method
add(java.util.function.Function<java.lang.String,java.lang.String>)
in fiandlambdas.AdderImpl and method
add(java.util.function.Consumer<java.lang.Integer>)
in fiandlambdas.AdderImpl match

为了解决这个问题,有两个办法。首先是改方法的名字:

1
2
3
String addWithFunction(Function<String, String> f);

void addWithConsumer(Consumer<Integer> f);

其次是类型转化,但是不推荐这样:

1
String r = Adder.add((Function) a -> a + " from lambda");

7.不要将lambda表达式视为内部类

尽管在之前的例子里,我们都是把Lambda表达式作为内部类来代替的。但是这两个概念有个重要的不同:范围(scope)

在内部类中,他会创建一个新的范围,你可以在内部的范围中通过创建相同名称的变量来覆盖本地变量。你还可以用this关键字来引用内部类的实例。

但是在lambda表达式中,你不能覆盖外部的变量,并且你用this引用的是类的属性,而不是lambda的。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private String value = "Enclosing scope value";

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;
}

如果你运行scopeExperiment()方法将得到

1
Results: resultIC = Inner class value, resultLambda = Enclosing scope value

8.保持lambda表达式短且不言自明(Self-explanatory)

如果可以的话,请使用一行结构,而不是一大块代码。Lambda应该是一种表达式,而不是一种叙述。Lambda应该精准的表达他提供的功能。

这只是风格建议,这并不会影响性能。但是一般而言这样更容易理解和使用。

这可以通过多钟方式实现,让我们仔细看看。

8.1 避免Lambda体内使用代码块

在理想的情况下,Lambda应该被写成一行。这样Lambda是一个不言自明的结构。在有参数的情况下,它声明应该用什么数据执行什么操作。

如果有一大块代码,Lambda的功能就不是那么清晰了。

按照这样的思想,应该使用下面的例子

1
Foo foo = parameter -> buildString(parameter);
1
2
3
4
5
private String buildString(String parameter) {
String result = "Something " + parameter; return result;
}

而不是

1
2
3
4
Foo foo = parameter -> { String result = "Something " + parameter;

    return result;
};

8.2避免指定参数类型

大多数情况编译器是可以自动推断参数类型的。

这样做

1
(a, b) -> a.toLowerCase() + b.toLowerCase();

而不是

1
(String a, String b) -> a.toLowerCase() + b.toLowerCase();

8.3避免在单个参数周围使用括号

Lambda表达式的语法只需要在多个参数和没有参数的时候使用括号。

这样做

1
a -> a.toLowerCase();

而不是

1
(a) -> a.toLowerCase();

8.4避免使用return表达式和大括号

在一行的Lambda中,return和大括号是可选的。

这样做

1
a -> a.toLowerCase();

而不是

1
a -> {return a.toLowerCase()};

8.5使用方法引用

通常(例如前面的例子)Lambda表达式都只是调用其他地方实现的方法。这种情况下使用Java 8的另一个功能:方法引用。

所以lambda

1
a -> a.toLowerCase();

可以被这样代替:

1
String::toLowerCase;

这样不一定总是更短,但是可以增强代码的可读性

9.修改对象的变量

lambdas的主要用途之一是用于并行计算 - 这意味着它们在线程安全方面非常有用。

如果非要改变对象变量的值,可以这样

1
2
3
int[] total = new int[1];
Runnable r = () -> total[0]++;
r.run();

保留此示例作为提醒,以避免可能导致意外突变的代码。

10.结论

在本文中,我们看到了Java 8的lambda表达式和函数式接口中的一些最佳实践和缺陷。尽管这些新功能具有实用性和强大功能,但它们只是工具。每个开发人员在使用它们时都应该注意

Lambda表达式和函数试接口的最佳实践 · LiangYongrui's Studio的更多相关文章

  1. [二] java8 函数式接口详解 函数接口详解 lambda表达式 匿名函数 方法引用使用含义 函数式接口实例 如何定义函数式接口

    函数式接口详细定义 package java.lang; import java.lang.annotation.*; /** * An informative annotation type use ...

  2. java8函数式接口详解、函数接口详解、lambda表达式匿名函数、方法引用使用含义、函数式接口实例、如何定义函数式接口

    函数式接口详细定义 函数式接口只有一个抽象方法 由于default方法有一个实现,所以他们不是抽象的. 如果一个接口定义了一个抽象方法,而他恰好覆盖了Object的public方法,仍旧不算做接口的抽 ...

  3. 【C++】C++中的lambda表达式和函数对象

    目录结构: contents structure [-] lambda表达式 lambda c++14新特性 lambda捕捉表达式 泛型lambda表达式 函数对象 函数适配器 绑定器(binder ...

  4. C#利用lambda表达式将函数作为参数或属性跨类传递

    在编码时,由于开始是在winform下进行简单的测试开发的,后来代码多了,就想分到不同的类里边去,可是因为原来的测试是在同一个form下的,所以对于函数调用可以很方便,而一旦跨类之后,就会发现,这函数 ...

  5. lambda表达式 匿名函数

    lambda函数是一种快速定义单行最小函数的方法,是从Lisp借鉴而来的,可以用在任何需要函数的地方. 基础 lambda语句中,冒号前是参数,可以有多个,用逗号分割:冒号右边是返回值. lambda ...

  6. Python:lambda表达式(匿名函数)

    lambda表达式: 通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数. 当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便. 在Python中 ...

  7. lambda表达式与函数接口的关系以及使用案例

    lambda表达式与函数式接口是结合使用的. 函数式接口:接口中只有一个抽象方法的接口,其中可以包括default修饰,static 修饰的实例方法.函数式接口可以在接口上添加@FuncationIn ...

  8. 还看不懂同事的代码?Lambda 表达式、函数接口了解一下

    当前时间:2019年 11月 11日,距离 JDK 14 发布时间(2020年3月17日)还有多少天? // 距离JDK 14 发布还有多少天? LocalDate jdk14 = LocalDate ...

  9. lambda表达式匿名函数

    匿名函数是一个“内联”语句或表达式,可在需要委托类型的任何地方使用.可以使用匿名函数来初始化命名委托,或传递命名委托(而不是命名委托类型)作为方法参数. C# 中委托的发展 在 C# 1.0 中,您通 ...

随机推荐

  1. android蜂巢效果、环形菜单、Kotlin影视应用、简约时钟、查看导出App、支付宝AR扫码效果等源码

    Android精选源码 一个蜂巢布局管理器,外观帅气外,动画效果也是很赞 一个基础 UI 框架项目,实现不同布局格式的混排 仿建行app效果,一个环形菜单的布局管理器源码 基于组件化实现的一款用Kot ...

  2. IMX6开发板虚拟机加载Ubuntu12.04.2镜像

    基于迅为IMX6开发板安装好虚拟机之后,用户就可以加载 Ubuntu12.04.2 镜像.用户可以在网盘中下载“编译好的镜像”,该镜像已经安装好了编译 Android4.4.2 所需要的大部分软件.用 ...

  3. php 连接oracle插入多张图片的方法

    php连接oracle数据库的时候,其查询.更新.删除数据和MySQL类似,但是增加数据.特别是图片的时候就很不一样,这里面涉及到要创建一个blob对象,用blod对象去保存php图片,下面是当插入多 ...

  4. EternalBlue永恒之蓝漏洞复现

    EternalBlue漏洞复现 1.    实训目的 永恒之蓝(EternalBlue)是由美国国家安全局开发的漏洞利用程序,对应微软漏洞编号ms17-010.该漏洞利用工具由一个名为”影子经济人”( ...

  5. MySQL_语句

    一.基础 1.说明:创建数据库CREATE DATABASE database-name 2.说明:删除数据库drop database dbname 3.说明:备份sql server--- 创建 ...

  6. ubuntu .bashrc文件添加jdk后无法登录的解决方案

    1. 快捷键(ctl-alt-f2)进入虚拟终端 2. 执行export PATH=/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/ ...

  7. 路由器协议----IGP、EGP、RIP、OSPF、BGP、MPLS

    1.路由控制的定义 <br>1.1.IP地址与路由控制   file:///var/folders/pz/cy11_lpd5rqfs66s778032580000gn/T/51.html ...

  8. 吴裕雄--天生自然python学习笔记:python下载安装各种模块的whl文件网址

    python下载安装各种模块的whl文件网址:https://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml

  9. EMCCD

    EMCCD 即电子倍增CCD,是探测领域内灵敏度极高的一种高端光电探测产品. 在光子探测领域的应用发展对探测器灵敏度的要求不断提高,EMCCD (Electron-Multiplying CCD)技术 ...

  10. mysql 数据库 创建用户和授权

    创建用户和密码 CREATE USER '用户名'@'%' IDENTIFIED BY '密码'; 创建几个数据库 例如 db1,db2 用户授权访问指定的数据库 grant all privileg ...