Java 8为什么需要Lambda表达式
来源:互联网 发布:通用顶级域名有几个 编辑:程序博客网 时间:2024/05/20 05:23
函数编程在C#、Python、JavaScript中都得到充分体现。而Java直到最新的Java 8才开始正式支持函数编程,最明显的改进就是对Lamba表达式的支持。正如C#之父Anders Hejlsberg在那篇文章 编程语言大趋势 中所讲,未来的编程语言将逐渐融合各自的特性,而不存在单纯的声明式语言(如之前的Java)或者函数编程语言。将来声明式编程语言借鉴函数编程思想,函数编程语言融合声明式编程特性...这几乎是一种必然趋势。如下图所示:
影响力较大的三个趋势
那具体而言我们为什么需要Lambda表达式呢?难道Java的OO和命令式编程(imperative programming)特性不够强大吗?下面让我们来分析下其原因。
1、内部循环和外部循环
先看一个大家耳熟能详的例子:
1
List<Integer> numbers = Arrays.asList(
1
,
2
,
3
,
4
,
5
,
6
);
2
3
for
(
int
number : numbers) {
4
System.out.println(number);
5
}
- 只能顺序处理List中的元素(process one by one)
- 不能充分利用多核CPU
- 不利于编译器优化
而如果利用内部循环,代码写成下面这样:
1
List<Integer> numbers = Arrays.asList(
1
,
2
,
3
,
4
,
5
,
6
);
2
3
numbers.forEach((Integer value) -> System.out.println(value));
- 不一定需要顺序处理List中的元素,顺序可以不确定
- 可以并行处理,充分利用多核CPU的优势
- 有利于JIT编译器对代码进行优化
类似的C#从4.0版本开始也支持集合元素并行处理,代码如下:
1
List<
int
> nums =
new
List<
int
> { 1, 2, 3, 4, 5 };
2
Parallel.ForEach(nums, (value) =>
3
{
4
Console.WriteLine(value);
5
});
2、传递行为,而不仅仅是传值
如果你使用C#有一段时间的话,那么你很可能已经明白这个标题的意思了。在C#中,经常看到一些函数的参数是Action或者Func类型,比如下面这个:
01
public
class
ArticleDac {
02
...
03
public
Article GetArticles(Func<IDbSet<Article>, Article> func)
// 这里传递的就是行为
04
{
05
using
(var db = xx) {
06
return
func(db.Articles);
07
}
08
}
09
...
10
}
11
// 下面是调用
12
int
articleId = 119;
13
var firstArticle =
new
ArticleDac().GetArticles(
14
articleDbSet =>
15
articleDbSet.AsQueryable().FirstOrDefault(x => x.id == articleId)
16
);
1
List<Integer> numbers = Arrays.asList(
1
,
2
,
3
,
4
,
5
,
6
);
2
3
public
int
sumAll(List<Integer> numbers) {
4
int
total =
0
;
5
for
(
int
number : numbers) {
6
total += number;
7
}
8
return
total;
9
}
1
public
int
sumAllEven(List<Integer> numbers) {
2
int
total =
0
;
3
for
(
int
number : numbers) {
4
if
(number %
2
==
0
) {
5
total += number;
6
}
7
}
8
return
total;
9
}
1
public
int
sumAllEven(List<Integer> numbers) {
2
int
total =
0
;
3
for
(
int
number : numbers) {
4
if
(number >
3
) {
5
total += number;
6
}
7
}
8
return
total;
9
}
比较这三个方法,我们发现了一个很明显的“代码臭味”—— 代码重复(详情参考《重构》),三个方法的唯一区别之处在于if判断这一行代码。如果脱离这里的上下文,我们会怎么做呢?我首先会先想到利用策略模式重构代码如下:
01
public
interface
Strategy {
02
public
boolean
test(
int
num);
03
}
04
05
public
class
SumAllStrategy {
06
public
boolean
test(
int
num) {
07
return
true
;
08
}
09
}
10
11
public
class
SumAllEvenStrategy {
12
public
boolean
test(
int
num) {
13
return
num %
2
==
0
;
14
}
15
}
16
17
public
class
ContextClass {
18
private
Strategy stragegy =
null
;
19
private
final
static
Strategy DEFAULT_STRATEGY =
new
SumAllStrategy();
20
21
public
ContextClass() {
22
this
(
null
);
23
}
24
25
public
ContextClass(Stragegy stragegy) {
26
if
(strategy !=
null
) {
27
this
.strategy = strategy;
28
}
29
else
{
30
this
.strategy = DEFAULT_STRATEGY;
31
}
32
}
33
34
public
int
sumAll(List<Integer> numbers) {
35
int
total =
0
;
36
for
(
int
number : numbers) {
37
if
(strategy.test(number)) {
38
total += number;
39
}
40
}
41
42
return
total;
43
}
44
}
45
46
47
// 调用
48
ContextClass context =
new
ContextClass();
49
context.sumAll(numbers);
设计模式在这里发挥了作用,OO特性还是蛮强大的!但是这是唯一的解决方案吗(当然不考虑用其他设计模式来解决,因为都是OO范畴!)?当然有,该轮到Java 8 Lambda表达式中的谓词(Predicate)该发挥作用了!
01
public
int
sumAll(List<Integer> numbers, Predicate<Integer> p) {
02
int
total =
0
;
03
for
(
int
number : numbers) {
04
if
(p.test(number)) {
05
total += number;
06
}
07
}
08
return
total;
09
}
10
11
sumAll(numbers, n ->
true
);
12
sumAll(numbers, n -> n %
2
==
0
);
13
sumAll(numbers, n -> n >
3
);
当然C#早已经支持这种用法,用C#改写上面的代码如下,是不是与上面的代码极其类似呢?
01
public
int
SumAll(List<
int
> numbers, Func<
int
,
bool
> func) {
02
var total = 0;
03
foreach
(var number
in
numbers) {
04
if
(func(number)) {
05
total += number;
06
}
07
}
08
09
return
total;
10
}
11
12
SumAll(numbers, n =>
true
);
13
SumAll(numbers, n => n % 2 == 0);
14
SumAll(numbers, n => n > 3);
3、Consumer与Loan Pattern
比如我们有一个资源类Resource:
01
public
class
Resource {
02
03
public
Resource() {
04
System.out.println(
"Opening resource"
);
05
}
06
07
public
void
operate() {
08
System.out.println(
"Operating on resource"
);
09
}
10
11
public
void
dispose() {
12
System.out.println(
"Disposing resource"
);
13
}
14
}
1
Resource resource =
new
Resource();
2
try
{
3
resource.operate();
4
}
finally
{
5
resource.dispose();
6
}
因为对资源对象resource执行operate方法时可能抛出RuntimeException,所以需要在finally语句块中释放资源,防止可能的内存泄漏。
但是有一个问题,如果很多地方都要用到这个资源,那么就存在很多段类似这样的代码,这很明显违反了DRY(Don't Repeat It Yourself)原则。而且如果某位程序员由于某些原因忘了用try/finally处理资源,那么很可能导致内存泄漏。那咋办呢?Java 8提供了一个Consumer接口,代码改写为如下:
01
public
class
Resource {
02
03
private
Resource() {
04
System.out.println(
"Opening resource"
);
05
}
06
07
public
void
operate() {
08
System.out.println(
"Operating on resource"
);
09
}
10
11
public
void
dispose() {
12
System.out.println(
"Disposing resource"
);
13
}
14
15
public
static
void
withResource(Consumer<Resource> consumer) {
16
Resource resource =
new
Resource();
17
try
{
18
consumer.accept(resource);
19
}
finally
{
20
resource.dispose();
21
}
22
}
23
}
1
Resource.withResource(resource -> resource.operate());
4、stream+laziness => efficiency
像之前一样先来一段非常简单的代码:
01
List<Integer> numbers = Arrays.asList(
1
,
2
,
3
,
4
,
5
,
6
);
02
03
for
(
int
number : numbers) {
04
if
(number %
2
==
0
) {
05
int
n2 = number *
2
;
06
if
(n2 >
5
) {
07
System.out.println(n2);
08
break
;
09
}
10
}
11
}
01
public
boolean
isEven(
int
number) {
02
return
number %
2
==
0
;
03
}
04
05
public
int
doubleIt(
int
number) {
06
return
number *
2
;
07
}
08
09
public
boolean
isGreaterThan5(
int
number) {
10
return
number >
5
;
11
}
12
13
for
(
int
number : numbers) {
14
if
(isEven(number)) {
15
int
n2 = doubleIt(number);
16
if
(isGreaterThan5(n2)) {
17
System.out.println(n2);
18
break
;
19
}
20
}
21
}
01
public
boolean
isEven(
int
number) {
02
return
number %
2
==
0
;
03
}
04
05
public
int
doubleIt(
int
number) {
06
return
number *
2
;
07
}
08
09
public
boolean
isGreaterThan5(
int
number) {
10
return
number >
5
;
11
}
12
13
List<Integer> l1 =
new
ArrayList<Integer>();
14
for
(
int
n : numbers) {
15
if
(isEven(n)) l1.add(n);
16
}
17
18
List<Integer> l2 =
new
ArrayList<Integer>();
19
for
(
int
n : l1) {
20
l2.add(doubleIt(n));
21
}
22
23
List<Integer> l3 =
new
ArrayList<Integer>();
24
for
(
int
n : l2) {
25
if
(isGreaterThan5(n)) l3.add(n);
26
}
27
28
System.out.println(l3.get(
0
));
1
isEven: 1
2
isEven: 2
3
doubleIt: 2
4
isGreaterThan5: 2
5
isEven: 3
6
isEven: 4
7
doubleIt: 4
8
isGreaterThan5: 4
9
8
而我们的第三版代码的执行流程是这样的:
01
isEven: 1
02
isEven: 2
03
isEven: 3
04
isEven: 4
05
isEven: 5
06
isEven: 6
07
doubleIt: 2
08
doubleIt: 4
09
doubleIt: 6
10
isGreaterThan5: 2
11
isGreaterThan5: 4
12
isGreaterThan5: 6
13
8
步骤数是13:9,所以有时候重构得到可读性强的代码可能会牺牲一些运行效率(但是一切都得实际衡量之后才能确定)。那么有没有“三全其美”的实现方法呢?即:
- 代码可读性强
- 代码执行效率不比第一版代码差
- 空间消耗小
Streams come to rescue! Java 8提供了stream方法,我们可以通过对任何集合对象调用stream()方法获得Stream对象,Stream对象有别于Collections的几点如下:
- 不存储值:Streams不会存储值,它们从某个数据结构的流水线型操作中获取值(“酒肉穿肠过”)
- 天生的函数编程特性:对Stream对象操作能得到一个结果,但是不会修改原始数据结构
- Laziness-seeking(延迟搜索):Stream的很多操作如filter、map、sort和duplicate removal(去重)可以延迟实现,意思是我们只要检查到满足要求的元素就可以返回
- 可选边界:Streams允许Client取足够多的元素直到满足某个条件为止。而Collections不能这么做
上代码:
1
System.out.println(
2
numbers.stream()
3
.filter(Lazy::isEven)
4
.map(Lazy::doubleIt)
5
.filter(Lazy::isGreaterThan5)
6
.findFirst()
7
);
1
isEven: 1
2
isEven: 2
3
doubleIt: 2
4
isGreaterThan5: 4
5
isEven: 3
6
isEven: 4
7
doubleIt: 4
8
isGreaterThan5: 8
9
IntOptional[8]
1
Stream流对象要经过下面这种流水线式处理:
2
过滤出偶数 => 乘以2 => 过滤出大于5的数 => 取出第一个数
3
4
注意:=> 左边的输出是右边的输入
IntOptional[8]只是简单包装了下返回的结果,这样有什么好处呢?如果你接触过Null Object Pattern的话就知道了,这样可以避免无谓的null检测。
本文完,希望对大家有所帮助,O(∩_∩)O
http://www.open-open.com/lib/view/open1365053088359.html- Java 8为什么需要Lambda表达式
- Java 8为什么需要Lambda表达式
- Java 8为什么需要Lambda表达式
- Java 8为什么需要Lambda表达式
- Java 8为什么需要Lambda表达式
- Java 8为什么需要Lambda表达式
- Java SE8 Lambda 基础入门---为什么需要lambda
- 为什么Java要增加lambda表达式
- Lambda FAQ_2.为什么在Java中加入Lambda表达式?
- Java 8 lambda表达式
- Java 8 Lambda表达式
- Java 8 lambda表达式
- Java 8 Lambda 表达式
- Java 8 Lambda表达式
- Java 8 Lambda 表达式
- Java 8 Lambda表达式
- Java 8 Lambda 表达式
- Java 8 Lambda 表达式
- iOS安全攻防(十九):基于脚本实现动态库注入
- Linux控制台汉化Fbterm和Yong .
- 强制产生死锁的进程结束,产生core文件
- sth about Hadoop
- ZJU3686 线段树
- Java 8为什么需要Lambda表达式
- Nginx常用命令
- android颜色对应的xml配置值,颜色表
- cocos2d-x中为什么要用sharedXX()函数创建单例类的static对象
- Java反射机制 笔记
- 产品运营小议
- poj3260
- 397高校毕业设计选题
- sjtu oj 1002.二哥种花生