案例学习 Java8 Lambda函数式编程

本文通过案例,快速展示如何从传统编程方式演化为函数式编程。
本文虽然不能完全掌握整个函数式编程的技术与方法,但重点在于了解一种解编程思维方式的转变。其中涉及的概念和思路也将成为学习入门的引子。
文中案例和思路源于《Java 8实战》并进行适当改编。希望全面了解掌握Java8和函数式编程技术的童鞋,强烈建议阅读此书。

需求背景:

有一个果园,库存(inventory)里有很多苹果,农民伯伯希望对库存进行排序,比较苹果的重量。

即:有一个Apple类型的集合列表(List<Apple>),我们希望用不同的排序策略对列表排序,比如根据重量(weight) 排序。

List<Apple> inventory = Arrays.asList(
		new Apple(80,"green"),
		new Apple(155, "green"),
		new Apple(120, "red")
);
1.方案一:原始方法
我们知道对一个List进行排序,最常用的方法之一是使用Collections提供的两种排序方法:
(1)Collections.sort(List<T> list, Comparator<? super T> c)
(2)Collections.sort(List<T>)
该方法中的List中的T必须实现Comparable<T>接口,然后实现compareTo()方法。

用第一种方式实现如下:

Collections.sort(inventory, new Comparator<Apple>() {
    @Override  
    public int compare(Apple a1, Apple a2) {  
        return a1.getWeight().compareTo(a2.getWeight());
    }  
});

2.方案二:用Java8的排序接口

在Java 8中,List自带了一个sort方法。方法声明如下:

    void sort(Comparator<? super E> c)
它需要一个Comparator对象来比较两个Apple!这就是在Java中传递策略的方式:把“行为策略”包裹在一个对象里。

先看下我们的实现:

public class AppleComparator implements Comparator<Apple> {
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }
}
inventory.sort(new AppleComparator());
注意这里的编程方式:首先定义了一个“比较行为(策略)”,然后将行为对象作为参数传递为sort方法。如果我们去查看sort的实现源码,将会看到底层会有c.compare()的调用。
这里引出一个概念叫:行为参数化。我们说sort的行为被参数化了:传递给它的排序策略不同,其行为也会不同,即Comparator的“实现”不同,执行sort的结果也不同 
行为参数化是实现函数式编程的一个重要思路。
3.方案三:使用匿名类

在上一个方案中,Comparator接口只被实例化一次,所以我们可以考虑采用类似方案一那种匿名类的方式来使代码更紧凑:

inventory.sort(new Comparator<Apple>() {
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }
});
4.方案四:使用Lambda表达式
目前为止,我们的实现都是可行的,但存在一个不大不小的问题:啰嗦。
Java8为了解决这个问题(当然不仅仅是个原因),引入了Lambda表达式。

我们先看下使用Lambda表达式的写法来实现我们的解决方案(我相信Lambda绝对是某个懒惰的程序员开发出来的):

inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
我们的代码看上去简洁多了。
如果是刚接触函数式编程的人员,这个时候肯定遇到些困惑——中间那个箭头是什么意思?箭头左边那一坨是什么意思?箭头右边那一坨又是以什么形式存在的?
好吧,短短几几句话是无法回答这个问题的,你需要一本书或者一个搜索引擎。但为了不耽误我们的进度,你可以先忽略这些概念和形式,只需要知道这仅仅是一种新的表达语法,其作用与上面使用匿名类相同,只是换了种写法:箭头前面括号部分就是方法的参数。箭头右边是方法体。箭头只是分隔符,让你分清“左右”。

不仅如此,新写法还有更强大的功能,它能自动根据上下文识别参数类型,所以上述表达式还能进一步简化,省去了参数类型

inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
5.方案五:更加简洁已读
好的代码往往兼顾两个方面:性能与可读性。可读性除了良好的代码规范之外,还指通过阅读代码就能体现业务逻辑和需求。
那么,我们的代码还能变得更易读一些吗?答案是肯定的。

Comparator具有一个叫作comparing的静态辅助方法,它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象(接口可以有静态方法)。它可以像下面这样用(注意你现在传递的Lambda只有一个参数:Lambda说明了如何从苹果中提取需要比较的键值):

Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight());

现在又可以把代码再改得紧凑一点了:

import static java.util.Comparator.comparing;//引入包

inventory.sort(comparing((a) -> a.getWeight()));

6.方案六:使用方法引用
我们已经简化了代码,甚至算是简化到极致了!?但还不够!
再了解一个概念:方法引用。
同 Lambda表达式一样,方法引用并非高级方法,还是那个懒惰的程序员为了少写代码而想出来的新的表示方式。比起使用Lambda表达式,用“方法引用”更易读,感觉也更自然。
方法引用其实可以简单的理解为Lambda的一种快捷写法。它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。

无论如何,使用方法引用之后,我们的代码最后就编程这个样子了:

inventory.sort(comparing(Apple::getWeight));
至此,我们已经通过程序实现并表达了“对库存进行排序,比较苹果的重量”这个需求。
分享到:

6 条评论

点击这里取消回复。

昵称
  1. 上海建站公司

    这种代码挺难的了

  2. 匿名

    test1

  3. 匿名

    test

  4. 匿名

    好复杂

    1. 匿名

      test

  5. 匿名

    收藏下