- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章深入浅析JDK8新特性之Lambda表达式由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
第一次是接触Lambda表达式是在TypeScript中(JavaScript的超集中),当时是为了让TypeScript的this方法外而不是本方法内所使用的。使用过后突然想到Lambda不是JDK8的重量级新特性么?于是感觉查阅相关资料并记录下来
一. 行为参数化 。
行为参数化简单的说就是函数的主体仅包含模板类通用代码,而一些会随着业务场景而变化的逻辑则以参数的形式传递到函数之中,采用行为参数化可以让程序更加的通用,以应对频繁变更的需求.
考虑一个业务场景,假设我们需要通过程序对苹果进行筛选,我们先定义一个苹果的实体:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public
class
Apple {
/** 编号 */
private
long
id;
/** 颜色 */
private
Color color;
/** 重量 */
private
float
weight;
/** 产地 */
private
String origin;
public
Apple() {
}
public
Apple(
long
id, Color color,
float
weight, String origin) {
this
.id = id;
this
.color = color;
this
.weight = weight;
this
.origin = origin;
}
// 省略getter和setter
}
|
用户最开始的需求可能只是简单的希望能够通过程序筛选出绿色的苹果,于是我们可以很快的通过程序实现:
1
2
3
4
5
6
7
8
9
|
public
static
List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> filterApples =
new
ArrayList<>();
for
(
final
Apple apple : apples) {
if
(Color.GREEN.equals(apple.getColor())) {
filterApples.add(apple);
}
}
return
filterApples;
}
|
这段代码很简单,没有什么值得说的。但当如果用户需求变为绿色,看起来修改代码也很简单,无非是把判断条件的绿色改为红色而已。但我们需要考虑另外一个问题,如果变化条件频繁的改变这么办?如果只是颜色的改变,那好我们直接让用户把颜色的判断条件传递进来,判断方法的参数变”要判断的集合以及要筛选的颜色”。但如果用户不仅仅是判断颜色,还想判断重量呀,大小呀什么的,怎么办?你是不是觉得我们依次添加不同的参数来完成判断就可以了?但这样通过传递参数的方式真的好吗?如果筛选条件越来越多,组合模式越来越复杂,我们是不是需要考虑到所有的情况,并针对每一种情况都有相应的应对策略呢?这个时候我们就可以将行为参数化,筛选条件抽离出来当做参数传递进来,此时我们可以封装一个判断的接口出来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public
interface
AppleFilter {
/**
* 筛选条件抽象
*
* @param apple
* @return
*/
boolean
accept(Apple apple);
}
/**
* 将筛选条件封装成接口
*
* @param apples
* @param filter
* @return
*/
public
static
List<Apple> filterApplesByAppleFilter(List<Apple> apples, AppleFilter filter) {
List<Apple> filterApples =
new
ArrayList<>();
for
(
final
Apple apple : apples) {
if
(filter.accept(apple)) {
filterApples.add(apple);
}
}
return
filterApples;
}
|
通过上面行为抽象化之后,我们可以在具体调用的地方设置筛选条件,并将条件作为参数传递到方法中,此时采用匿名内部类的方法:
1
2
3
4
5
6
7
8
9
10
11
|
public
static
void
main(String[] args) {
List<Apple> apples =
new
ArrayList<>();
// 筛选苹果
List<Apple> filterApples = filterApplesByAppleFilter(apples,
new
AppleFilter() {
@Override
public
boolean
accept(Apple apple) {
// 筛选重量大于100g的红苹果
return
Color.RED.equals(apple.getColor()) && apple.getWeight() >
100
;
}
});
}
|
这样的设计在jdk内部也经常采用,比如Java.util.Comparator,java.util.concurrent.Callable等,使用这一类接口的时候,我们都可以在具体调用的地方用过匿名类来指定函数的具体执行逻辑,不过从上面的代码块来看,虽然很极客,但是不够简洁,在java8中我们可以通过lambda来简化:
1
2
3
4
|
// 筛选苹果
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >=
100
);
//()->xxx ()里面就是方法参数,xxx是方法实现
|
二. lambda表达式定义 。
我们可以将lambda表达式定义为一种 简洁、可传递的匿名函数,首先我们需要明确lambda表达式本质上是一个函数,虽然它不属于某个特定的类,但具备参数列表、函数主体、返回类型,以及能够抛出异常;其次它是匿名的,lambda表达式没有具体的函数名称;lambda表达式可以像参数一样进行传递,从而极大的简化代码的编写。格式定义如下:
格式一: 参数列表 -> 表达式 。
格式二: 参数列表 -> {表达式集合} 。
需要注意的是,lambda表达式隐含了return关键字,所以在单个的表达式中,我们无需显式的写return关键字,但是当表达式是一个语句集合的时候,则需要显式添加return,并用花括号{ }将多个表达式包围起来,下面看几个例子:
1
2
3
4
5
6
7
8
9
|
//返回给定字符串的长度,隐含return语句
(String s) -> s.length()
// 始终返回42的无参方法
() ->
42
// 包含多行表达式,则用花括号括起来
(
int
x,
int
y) -> {
int
z = x * y;
return
x + z;
}
|
三. 依托于函数式接口使用lambda表达式 。
lambda表达式的使用需要借助于函数式接口,也就是说只有函数式接口出现地方,我们才可以将其用lambda表达式进行简化.
自定义函数式接口
函数式接口定义为只具备 一个抽象方法 的接口。java8在接口定义上的改进就是引入了默认方法,使得我们可以在接口中对方法提供默认的实现,但是不管存在多少个默认方法,只要具备一个且只有一个抽象方法,那么它就是函数式接口,如下(引用上面的AppleFilter):
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/**
* 苹果过滤接口
*/
@FunctionalInterface
public
interface
AppleFilter {
/**
* 筛选条件抽象
*
* @param apple
* @return
*/
boolean
accept(Apple apple);
}
|
。
AppleFilter仅包含一个抽象方法 accept(Apple apple),依照定义可以将其视为一个函数式接口,在定义时我们为该接口添加了@FunctionalInterface注解,用于标记该接口是函数式接口,不过这个接口是可选的,当添加了该接口之后,编译器就限制了该接口只允许有一个抽象方法,否则报错,所以推荐为函数式接口添加该注解.
jdk自带的函数式接口
jdk为lambda表达式已经内置了丰富的函数式接口,下面分别就Predicate<T>、Consumer<T>、Function<T, R>的使用示例说明.
Predicate
1
2
3
4
5
6
7
8
9
10
11
|
@FunctionalInterface
public
interface
Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean
test(T t);
}
|
Predicate的功能类似于上面的AppleFilter,利用我们在外部设定的条件对于传入的参数进行校验,并返回验证结果boolean,下面利用Predicate对List集合的元素进行过滤:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/**
*
* @param list
* @param predicate
* @param <T>
* @return
*/
public
<T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> newList =
new
ArrayList<T>();
for
(
final
T t : list) {
if
(predicate.test(t)) {
newList.add(t);
}
}
return
newList;
}
|
使用
1
|
demo.filter(list, (String str) ->
null
!= str && !str.isEmpty());
|
Consumer 。
1
2
3
4
5
6
7
8
9
10
|
@FunctionalInterface
public
interface
Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void
accept(T t);
}
|
Consumer提供了一个accept抽象函数,该函数接收参数,但不返回值,下面利用Consumer遍历集合. 。
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* 遍历集合,执行自定义行为
*
* @param list
* @param consumer
* @param <T>
*/
public
<T>
void
filter(List<T> list, Consumer<T> consumer) {
for
(
final
T t : list) {
consumer.accept(t);
}
}
|
利用上面的函数式接口,遍历字符串集合,并打印非空字符串:
1
2
3
4
5
|
demo.filter(list, (String str) -> {
if
(StringUtils.isNotBlank(str)) {
System.out.println(str);
}
});
|
Function 。
1
2
3
4
5
6
7
8
9
10
|
@FunctionalInterface
public
interface
Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
|
Funcation执行转换操作,输入是类型T的数据,返回R类型的数据,下面利用Function对集合进行转换:
1
2
3
4
5
6
7
|
public
<T, R> List<R> filter(List<T> list, Function<T, R> function) {
List<R> newList =
new
ArrayList<R>();
for
(
final
T t : list) {
newList.add(function.apply(t));
}
return
newList;
}
|
其他
1
|
demo.filter(list, (String str) -> Integer.parseInt(str));
|
上面这些函数式接口还提供了一些逻辑操作的默认实现,留到后面介绍java8接口的默认方法时再讲吧~ 。
使用过程中需要注意的一些事情
类型推断
在编码过程中,有时候可能会疑惑我们的调用代码会去具体匹配哪个函数式接口,实际上编译器会根据参数、返回类型、异常类型(如果存在)等做正确的判定。 在具体调用时,在一些时候可以省略参数的类型,从而进一步简化代码:
1
2
3
4
5
6
7
|
// 筛选苹果
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >=
100
);
// 某些情况下我们甚至可以省略参数类型,编译器会根据上下文正确判断
List<Apple> filterApples = filterApplesByAppleFilter(apples,
apple -> Color.R
ED.equals(apple.getColor()) && apple.getWeight() >=
100
);
|
局部变量 。
上面所有例子我们的lambda表达式都是使用其主体参数,我们也可以在lambda中使用局部变量,如下 。
1
2
3
|
int
weight =
100
;
List<Apple> filterApples = filterApplesByAppleFilter(apples,
apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= weight);:
|
该例子中我们在lambda中使用了局部变量weight,不过在lambda中使用局部变量必须要求该变量 显式声明为final或事实上的final ,这主要是因为局部变量存储在栈上,lambda表达式则在另一个线程中运行,当该线程视图访问该局部变量的时候,该变量存在被更改或回收的可能性,所以用final修饰之后就不会存在线程安全的问题.
四. 方法引用 。
采用方法引用可以更近一步的简化代码,有时候这种简化让代码看上去更加的直观,先看一个例子:
1
2
3
4
5
|
/* ... 省略apples的初始化操作 */
// 采用lambda表达式
apples.sort((Apple a, Apple b) -> Float.compare(a.getWeight(), b.getWeight()));
// 采用方法引用
apples.sort(Comparator.comparing(Apple::getWeight));
|
方法引用通过::将方法隶属和方法自身连接起来,主要分为三类:
静态方法 。
1
|
(args) -> ClassName.staticMethod(args)
|
转换成 。
1
|
ClassName::staticMethod
|
参数的实例方法 。
1
|
(args) -> args.instanceMethod()
|
转换成 。
1
|
ClassName::instanceMethod
// ClassName是args的类型
|
外部的实例方法 。
1
|
(args) -> ext.instanceMethod(args)
|
转换成 。
1
|
ext::instanceMethod(args)
|
参考
http://www.codeceo.com/article/lambda-of-java-8.html 。
以上所述是小编给大家介绍的JDK8新特性之Lambda表达式,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我网站的支持! 。
原文链接:http://blog.csdn.net/canot/article/details/52951828 。
最后此篇关于深入浅析JDK8新特性之Lambda表达式的文章就讲到这里了,如果你想了解更多关于深入浅析JDK8新特性之Lambda表达式的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在用 yacc/bison 编写一个简单的计算器。 表达式的语法看起来有点像这样: expr : NUM | expr '+' expr { $$ = $1 + $3; } | expr '-'
我开始学习 lambda 表达式,并在以下情况下遇到了以下语句: interface MyNumber { double getValue(); } MyNumber number; nu
这两个 Linq 查询有什么区别: var result = ResultLists().Where( c=> c.code == "abc").FirstOrDefault(); // vs. va
如果我们查看 draft C++ standard 5.1.2 Lambda 表达式 段 2 说(强调我的 future ): The evaluation of a lambda-expressio
我使用的是 Mule 4.2.2 运行时、studio 7.5.1 和 Oracle JDK 1.8.0_251。 我在 java 代码中使用 Lambda 表达式,该表达式由 java Invoke
我是 XPath 的新手。我有网页的html源 http://london.craigslist.co.uk/com/1233708939.html 现在我想从上面的页面中提取以下数据 完整日期 电子
已关闭。这个问题是 off-topic 。目前不接受答案。 想要改进这个问题吗? Update the question所以它是on-topic用于堆栈溢出。 已关闭10 年前。 Improve th
我将如何编写一个 Cron 表达式以在每天上午 8 点和下午 3:30 触发?我了解如何创建每天触发一次的表达式,而不是在多个设定时间触发。提前致谢 最佳答案 你应该只使用两行。 0 8 * * *
这个问题已经有答案了: What do 3 dots next to a parameter type mean in Java? (9 个回答) varargs and the '...' argu
我是 python 新手,在阅读 BeautifulSoup 教程时,我不明白这个表达式“[x for x in titles if x.findChildren()][:-1]”我不明白?你能解释一
(?:) 这是一个有效的 ruby 正则表达式,谁能告诉我它是什么意思? 谢谢 最佳答案 正如其他人所说,它被用作正则表达式的非捕获语法,但是,它也是正则表达式之外的有效 ruby 语法。 在
这个问题在这里已经有了答案: Why does ++[[]][+[]]+[+[]] return the string "10"? (10 个答案) 关闭 8 年前。 谁能帮我处理这个 JavaSc
这个问题在这里已经有了答案: What is the "-->" operator in C++? (29 个答案) Java: Prefix/postfix of increment/decrem
这个问题在这里已经有了答案: List comprehension vs. lambda + filter (16 个答案) 关闭 10 个月前。 我不确定我是否需要 lambda 或其他东西。但是,
C 中的 assert() 函数工作原理对我来说就像一片黑暗的森林。根据这里的答案https://stackoverflow.com/a/1571360 ,您可以使用以下构造将自定义消息输出到您的断言
在this页,John Barnes 写道: If the conditional expression is the argument of a type conversion then effec
我必须创建一个调度程序,它必须每周从第一天上午 9 点到第二天晚上 11 点 59 分运行 2 天(星期四和星期五)。为此,我需要提供一个 cron 表达式。 0-0 0-0 9-23 ? * THU
我正在尝试编写一个 Linq 表达式来检查派生类中的属性,但该列表由来自基类的成员组成。下面的示例代码。以“var list”开头的 Process 方法的第二行无法编译,但我不确定应该使用什么语法来
此 sed 表达式将输入字符串转换为两行输出字符串。两条输出行中的每一行都由输入的子串组成。第一行需要转换成大写: s:random_stuff\(choice1\|choice2\){\([^}]*
我正在使用 Quartz.Net 在我的应用程序中安排我的工作。我只是想知道是否可以为以下场景构建 CRON 表达式: Every second between 2:15AM and 5:20AM 最
我是一名优秀的程序员,十分优秀!