设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

Java 8为什么需要Lambda表达式

2013-4-7 11:28| 发布者: joejoe0332| 查看: 1858| 评论: 0|原作者: oschina|来自: oschina

摘要:   函数编程在C#、Python、JavaScript中都得到充分体现。而Java直到最新的Java 8才开始正式支持函数编程,最明显的改进就是对Lamba表达式的支持。正如C#之父Anders Hejlsberg在那篇文章编程语言大趋势中所讲,未来 ...

3、Consumer与Loan Pattern

     比如我们有一个资源类Resource:

01public 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}
      我们必须这样调用:
1Resource resource = new Resource();
2try {
3    resource.operate();
4} finally {
5    resource.dispose();
6}

     因为对资源对象resource执行operate方法时可能抛出RuntimeException,所以需要在finally语句块中释放资源,防止可能的内存泄漏。

     但是有一个问题,如果很多地方都要用到这个资源,那么就存在很多段类似这样的代码,这很明显违反了DRY(Don't Repeat It Yourself)原则。而且如果某位程序员由于某些原因忘了用try/finally处理资源,那么很可能导致内存泄漏。那咋办呢?Java 8提供了一个Consumer接口,代码改写为如下:

01public 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}
      调用代码如下:
1Resource.withResource(resource -> resource.operate());
      外部要访问Resource不能通过它的构造函数了(private),只能通过withResource方法了,这样代码清爽多了,而且也完全杜绝了因人为疏忽而导致的潜在内存泄漏。


4、stream+laziness => efficiency

     像之前一样先来一段非常简单的代码:

01List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
02 
03for (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}
      这段代码有什么问题? 没错,可读性非常差。第一步,我们利用《重构》一书中的最基础的提取小函数重构手法来重构代码如下:
01public boolean isEven(int number) {
02    return number % 2 == 0;
03}
04 
05public int doubleIt(int number) {
06    return number * 2;
07}
08 
09public boolean isGreaterThan5(int number) {
10    return number > 5;
11}
12 
13for (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}
      OK,代码的意图清晰多了,但是可读性仍然欠佳,因为循环内嵌套一个if分支,if分支内又嵌套另外一个分支,于是继续重构代码如下:
01public boolean isEven(int number) {
02    return number % 2 == 0;
03}
04 
05public int doubleIt(int number) {
06    return number * 2;
07}
08 
09public boolean isGreaterThan5(int number) {
10    return number > 5;
11}
12 
13List<Integer> l1 = new ArrayList<Integer>();
14for (int n : numbers) {
15    if (isEven(n)) l1.add(n);
16}
17 
18List<Integer> l2 = new ArrayList<Integer>();
19for (int n : l1) {
20    l2.add(doubleIt(n));
21}
22 
23List<Integer> l3 = new ArrayList<Integer>();
24for (int n : l2) {
25    if (isGreaterThan5(n)) l3.add(n);
26}
27 
28System.out.println(l3.get(0));
      现在代码够清晰了,这是典型的“流水线”风格代码。但是等等,现在的代码执行会占用更多空间(三个List)和时间,我们来分析下。首先第二版代码的执行流程是这样的:
1isEven: 1
2isEven: 2
3doubleIt: 2
4isGreaterThan5: 2
5isEven: 3
6isEven: 4
7doubleIt: 4
8isGreaterThan5: 4
98

     而我们的第三版代码的执行流程是这样的:

01isEven: 1
02isEven: 2
03isEven: 3
04isEven: 4
05isEven: 5
06isEven: 6
07doubleIt: 2
08doubleIt: 4
09doubleIt: 6
10isGreaterThan5: 2
11isGreaterThan5: 4
12isGreaterThan5: 6
138

     步骤数是13:9,所以有时候重构得到可读性强的代码可能会牺牲一些运行效率(但是一切都得实际衡量之后才能确定)。那么有没有“三全其美”的实现方法呢?即:

  1. 代码可读性强
  2. 代码执行效率不比第一版代码差
  3. 空间消耗小

     Streams come to rescue! Java 8提供了stream方法,我们可以通过对任何集合对象调用stream()方法获得Stream对象,Stream对象有别于Collections的几点如下:

  1. 不存储值:Streams不会存储值,它们从某个数据结构的流水线型操作中获取值(“酒肉穿肠过”
  2. 天生的函数编程特性:对Stream对象操作能得到一个结果,但是不会修改原始数据结构
  3. Laziness-seeking(延迟搜索):Stream的很多操作如filter、map、sort和duplicate removal(去重)可以延迟实现,意思是我们只要检查到满足要求的元素就可以返回
  4. 可选边界:Streams允许Client取足够多的元素直到满足某个条件为止。而Collections不能这么做

     上代码:

1System.out.println(
2    numbers.stream()
3            .filter(Lazy::isEven)
4            .map(Lazy::doubleIt)
5            .filter(Lazy::isGreaterThan5)
6            .findFirst()
7);
      现在的执行流程是:
1isEven: 1
2isEven: 2
3doubleIt: 2
4isGreaterThan5: 4
5isEven: 3
6isEven: 4
7doubleIt: 4
8isGreaterThan5: 8
9IntOptional[8]
      流程基本和第二版代码一致,这归功于Laziness-seeking特性。怎么理解呢?让我来构造下面这个场景:
1Stream流对象要经过下面这种流水线式处理:
2过滤出偶数 => 乘以2 => 过滤出大于5的数 => 取出第一个数
3 
4注意:=> 左边的输出是右边的输入
      而Laziness-seeking意味着 我们在每一步只要一找到满足条件的数字,马上传递给下一步去处理并且暂停当前步骤。比如先判断1是否偶数,显然不是;继续判断2是否偶数,是偶数;好,暂停过滤偶数操作,将2传递给下一步乘以2,得到4;4继续传递给第三步,4不满足大于5,所以折回第一步;判断3是否偶数,不是;判断4是否偶数,是偶数;4传递给第二步,乘以2得到8;8传递给第三步,8大于5;所以传递给最后一步,直接取出得到IntOptional[8]。

     IntOptional[8]只是简单包装了下返回的结果,这样有什么好处呢?如果你接触过Null Object Pattern的话就知道了,这样可以避免无谓的null检测。

     本文完,希望对大家有所帮助,O(∩_∩)O

参考自:

http://java.dzone.com/articles/why-we-need-lambda-expressions

http://java.dzone.com/articles/why-we-need-lambda-expressions-0


酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部