前言
函数式编程:没有共享的可变数据 + 将方法和函数(代码)作为一等值来传递
行为参数化:一个方法接收多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力
名词解释:
predicate(谓词):函数以及方法引用。
lambda表达式:匿名函数
java8主要的新特性:
- lambda表达式
- 方法引用
- 流(Collections用来存储和访问数据 ;Stream描述对数据的计算,用来并行的处理数据)
- 默认方法(扩展接口)
通过行为参数传递代码
将行为参数化是可以帮助我们处理频繁变更的需求的一种软件开发模式,它能够轻松的适应不断变化的需要,可以把一个行为(一段代码)封装起来,并通过传递和使用创建的行为(创建实现谓词的接口的类的实例)将方法的行为参数化。
在java8之前,我们会定义一个接口(谓词)来将行为参数化。然后我们创建不同的实现类继承该接口来实现不同的行为,最后将不同实现类的实例对象传递给对应的函数就完成了行为(代码)传递工作。这种做法就类似于在内联”传递代码”,将需要传递的代码包裹在一个实现了接口的类里面传递给调用的方法。
同时为了改善代码,让它更加简洁,我们面对很多只要实例化一次的类,可以采用匿名类来同时声明和实例化一个类,也就是说匿名类允许我们随建随用。
但是使用匿名类还是不够好,代码不易读,我们还是需要创建一个对象,明确地实现一个方法来定义一个新的行为。为了更加契合软件工程中DPY原则(Don’t Repeat Yourself),java8引人了lambda表达式(匿名函数)!
Lambda表达式
Lambda表达式:匿名的方法,可以作为参数传递或者存储在变量中
Lambda表达式语法结构:
- (parameters) -> expression
- (parameters) -> {statements;}
parameters是参数列表,后续是Lambda表达式的主体,主体内如果是表达式则不需要花括号和分号,如果是语句的化则需要花括号与分号。注意区分。
例子:
|
|
我们使用Lambda表达式(直接赋值给变量)
|
|
在哪可以使用Lambda表达式
函数式接口:只定义了一个抽象方法的接口;
在定义函数式接口时我们为该接口添加了@FunctionalInterface注解,用于标记该接口是函数式接口,不过这个接口是可选的,当添加了该接口之后,编译器就限制了该接口只允许有一个抽象方法,否则报错,所以推荐为函数式接口添加该注解。
Lambda表达式允许我们直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。(具体来说,是函数式接口的一个具体实现的实例)当然,我们使用匿名类同样可以完成这个操作,只不过是比较笨拙。
代码示例:
|
|
函数描述符:函数式接口的抽象方法的签名基本上就是Lambda表达式的签名,这种抽象方法叫作函数描述符。
JavaApi中常用的函数式接口(只列举了一部分,最终以java官方的javadoc为主):
我们知道java的类型,要不是引用类型,要不是原始数据类型(int,double等等)。但是由于历史原因,泛型只能绑定到引用类型。为了使用原始类型的数据,java引人了自动装箱和自动拆箱的优化,但是装箱和拆箱的过程是对性能有损失的。
于是,java8为了避免在使用函数式接口的时候发生装箱和拆箱的操作,专门定义了原始数据类型的接口,叫作原始类型的特化版本。例如:
|
|
关于Lambda表达式使用局部变量:
Lambda表达式可以无限制的捕获(在其主体中引用)实例变量和静态变量,但是局部变量必须显式声明为final或事实上是final的。因为实例变量存储在堆中,局部变量保存在栈上,Lambda表达式在另一个线程里面执行,当线程试图访问该局部变量的时候,变量存在着被修改和收回的可能,所以用final修饰就不会存在线程安全的问题了。
简单介绍方法引用
方法引用可以重复使用现有的方法定义,并像Lambda一样传递。方法引用相当于仅仅涉及单一方法的Lambda表达式的语法糖,是一种快捷的写法。
方法引用实例代码:
|
|
复合Lambda表达式的有用方法
比较器复合:比较器链或者逆序。参考Comparator的Api
谓词复合:negate,and,or。可以重用已有的Predicate来创建新的谓词。negate表示一个Predicate的非;and用于把2个Lambda组合起来;or表示2个Lambda的或逻辑。
函数复合:把Function接口所代表的Lambda表达式复合。有andThen和compose2个默认方法。具体区别参考javadoc文档。