Java8实战之行为参数化与Lambda
前言
现在Java的迭代速度比以前快了很多,然而,本渣渣最近才开始学习Java8,相比于之前的版本,Java8中引入了许多新的特性,其中最主要的是Lambda
、Stream
、Optional<T>
、新的时间日期API,接下来将分成几个小节,分别学习这些内容,并且将学习笔记整理出来,参考书籍为《Java8实战》。
本小节主要学习行为参数化以及Lambda
行为参数化
在Java8之前,Java中的方法或者说函数是二等公民,也就是说,方法或者函数是无法作为参数进行传递的,随着编程思想的逐步发展,函数式编程的思想越来越受到重视,其优势也逐渐凸显出来,Java8引入了对应的解决方法,提供了一种类似的方式来处理,使得可以将函数作为参数进行传递,从而实现了类似函数式编程的功能,亦即Lamda。
所谓的形式参数化,就是将行为,通常表现为函数或者方法做为参数进行传递,这种编程方式的好处在于,可以将变化的部分抽取出来,形成函数,然后根据情况传递实现的内容,使得整体具有更高的灵活性。
下面的举例内容大致来自于书中的例子,通过该例子,可以看出行为参数化的优势
class Apple { private int weight; private String color; // 省略set、get方法}
对于上面的Apple类,如果我们想根据不同的情况进行筛选,实现方式有多种,一种是根据需要筛选的条件提供对应的筛选方法,如下所示
public List<Apple> filterAppleByWeight(List<Apple> apples) { List<Apple> result = new ArrayList<>(); for (Apple apple : apples) { if (apple.getWeight() > 30) { result.add(apple); } } return result; }public List<Apple> filterAppleByColor(List<Apple> apples) { List<Apple> result = new ArrayList<>(); for (Apple apple : apples) { if (apple.getColor().equals("green")) { result.add(apple); } } return result; }
上面的方式看起来没有什么问题,而且非常直观,当需要增加新的过滤条件时,只需要复制一份,然后更改过滤条件就行,但实际上这种方式问题是比较大的
存在着明显的代码冗余,代码冗余就意味着如果需要修改,需要注意的地方会比较多
不利于变化,比如说现在需要过滤
weight > 30 && color == "green"
的,那么就需要为其再编写对应的处理函数,而改变的仅仅是其中的一行代码。尤其是当属性比较多的时候,各种情况组合起来,需要考虑的情况非常多,代码也会变得非常复杂。
由于上面的方式比较难以应对各种情况,所以,更好地方式是采用类似策略模式的形式,将其中可能经常变化的部分抽取出来放在接口里面,在运行时通过传入不同的实现类来处理
// 过滤器接口interface AppleFilter { boolean filter(Apple apple); }// 过滤器实现class GreenAppleFilter implements AppleFilter { @Override public boolean filter(Apple apple) { return "green".equals(apple.getColor()); } }// 过滤苹果操作public List<Apple> static filterApple(List<Apple> apples, AppleFilter appleFilter) { List<Apple> result = new ArrayList<>(); for (Apple apple : apples) { if (appleFilter.filter(apple)) { result.add(apple); } } return result; }public void operation() { AppleFilter appleFilter = new GreenAppleFilter(); filterApple(apples, appleFilter); }
通过上面的方式,可以在需要的时候创建对应的过滤器实现类,然后传递给过滤操作函数即可,通过这种方式,可以避免前面出现的修改问题。
然而,上面的这种方式其实并不优雅,也不方便,由于我们需要根据不同的情况,编写不同的实现类,这虽然方便解耦,但却增大了不必要的工作量。
通过匿名内部类的形式,我们就可以在不编写对应实现类的情况下,直接将对应的实现传递给对应的方法,所以,上面的代码可以直接简化为
public void operation() { // 匿名内部类实现 filterApple(apples, new AppleFilter() { @Override public boolean filter(Apple apple) { return "green".equals(apple.getColor()); } }); }
这样就可以不用编写实现类,然后再来实现对应的操作了,这种方式已经是非常优雅了,不过,仍然不是最优雅的方式,因为我们还需要new
一个对象,然后实现其方法。
上面的实现方式是在Java8之前的方式,在Java8之后,通过Lambda,我们可以将上面的方式更佳地简化,最终代码如下所示
public void operation() { filterApple(apples, apple -> "green".equals(apple.getColor())); }
可以看到,Lambda表达式以及其简洁的方式来实现我们的需求,并且满足了对改动等的要求,所以,下面我们就详细地学习Lambda表达式。
Lambda表达式
Lambda表达式是在Java8之后引入的一种新的代码编写方式,通过Lambda表达式,可以在某种程度上使得代码变得更加简洁,尤其是在某个函数只需要使用一次的时候,无需再为他们设计专门的类以及方法。
Lambda表达式可以简单地理解匿名函数,所以Lambda表达式具有函数除了名字之外的的所有特性
可以有一个或者多个参数
可以具有返回值也可以没有
具有方法体,也就是Lambda所要执行的内容
将Lambda表达式理解为匿名函数之后,关于Lambda表达式的写法就比较好理解了,Lambda具有两种最基本的写法
// 表达式() -> EXP// 语句() -> {STATEMENT1; STATEMENT2; ...}
其中的->
用于分隔参数以及方法体,是必须具备的,如果只有一个参数,()
可以不用,如apple -> {}
和(apple) -> {}
是等价的,而且,在Lambda表达式参数中,可以不用写上类型,原因在于lambda可以根据使用的场景进行推导,后面展开。
注意上面的两种形式,其中的表达式没有{}
,并且不需要;
只能有一个表达式(表达式的返回值就是Lambda的值),而对于语句来讲可以有多个表达式,每个表达式后面需要有;
,最后一个表达式作为Lambda表达式的返回值。
函数接口
上面我们看到了Lambda的写法,接下来我们来看下Lambda表达式的应用场景,并不是说每个Lambda表达式都可以作为参数传递到任意的方法中,Lambda表达式需要符合某种规则(参数,返回值),而这些规则,就来自于函数接口。
函数接口是一种特别的接口,这种接口是专门为Lambda表达式创建的,其特性表现为
只能有一个抽象方法(可以有多个默认方法,关于默认方法,将在后面的小节学习到)
可以使用
@FunctionalInterface
,进行修饰
比如上面的AppleFilter
就是一个函数接口,而下面的接口就不是了,因为有不止一个抽象方法
interface AppleFilter { boolean filter(Apple apple); boolean match(Apple apple); }
在传递Lambda表达式的时候,在方法的参数中,需要提供一个函数接口,才能将Lambda表达式传递给该方法,并且该Lambda表达式必须与函数接口中的抽象方法签名一致,可以简单地理解为该Lambda表达式就是该接口的一个实现。
public void operation() { filterApple(apples, apple -> "green".equals(apple.getColor())); // 其中的Lambda表达式 apple -> "green".equals(apple.getColor()); // 跟接口中的抽象方法签名 boolean filter(Apple apple); 是一致的 // 一个参数,一个Boolean类型的返回值}
同时我们也提到了Lambda表达式参数可以不用写类型,可以根据情况进行推导,其原因也在于此,当我们把一个Lambda表达式传递给一个方法的时候,由于该方法中的函数接口中的匿名方法签名已经是确定的了,比如上面的boolean filter(Apple apple)
,所以,当使用Lambda表达式 apple -> "green".equals(apple.getColor())
的时候,编译器可以根据方法签名中的方法类型来推导出Lambda表达式中的参数签名,进而对后面的内容进行验证,参数个数以及返回值也是如此,而且可以验证整个Lambda表示式是否是符合对应抽象方法签名的。
这里有个地方需要注意,同一个Lambda表示式可以用在不同的函数接口的,只需要符合其方法签名即可,同时,一个Lambda表达式可以直接赋值给其对应的函数接口对象
interface AppleFilter { boolean filter(Apple apple); }interface Predict { boolean test(Apple apple); }// 上面的Lambda表达式可以用于这两个接口// 如果结合泛型的话,则同一个Lambda表达式可以使用的场景就更多了
由于如果每次需要,都要设计对应的函数接口,也是一件非常麻烦的事情,所以,在JDK中提供了基本上涵盖了我们所需要的函数接口了,可以根据需要使用即可,其中常用的几个如下所示
public interface Consumer<T> { void accept(T t); }public interface Function<T, R> { R apply(T t); }public interface Supplier<T> { T get(); }public interface Predicate<T> { boolean test(T t); }// 更多的函数接口可以参考 package java.util.function
作者:颜洛滨
链接:https://www.jianshu.com/p/3db415bb6751