函数式编程,英文翻译为Functional Programming,函数式编程中所说的”函数”,并不是我们说的编程语言中的函数的概念,而是一种数学上的概念,一个函数,或者表达式,我们将数学上的函数 或者 表达式,设计为一个类上的函数,所以也是某种含义上的函数
每个编程范式都有自己独特的地方,这就是他们会被抽取出来为一种范式的原因,面向对象编程最大的特点就是,以类,对象作为组织代码的单元以及它的四大特性,面向过程编程的特点就是以函数为组织代码的单元,数据与方法相分离,函数式编程最独特的地方在于是一种数学函数上的概念,函数式编程是程序面向数学更底层的抽象,计算过程描述为表达式
在一些科学计算,数据处理,统计分析的领域,函数的编程可以让更少的代码做更多的事情,但是对于一些业务复杂的逻辑,抽象为数学表达式,可以说是自讨苦吃
函数式编程和面向过程的编程相比较,其函数是一种弄无状态的,就是函数内部涉及的变量都是无状态,所谓无状态的,就是函数内部涉及的变量都是局部变量,不是像面向对象编程那样,共享类成员变量,也不是像面向过程编程那样,共享全局变量,函数的执行结果之和入参有关,和其他的外部变量无关,同样的入参,不管怎么执行,得到的结果都是一样的,就是数学函数的样子
// 有状态函数: 执行结果依赖b的值是多少,即便入参相同,多次执行函数,函数的返回值有可能不同,因为b值有可能不同。
int b; int increase(int a) { return a + b; } // 无状态函数:执行结果不依赖任何外部变量值,只要入参相同,不管执行多少次,函数的返回值就相同 int increase(int a, int b) { return a + b; } |
Java对于函数式编程的支持
实现面向对象的编程并不一定用面向对象编程,同理,实现函数式编程也不一定非得使用函数式编程原因,现在很多的面向对象的编程语言,也提供了相对应的语法,类库来支持函数式编程
在Java中,也提供了函数式编程的支持,下面就是一段典型的Java函数式编程的代码
public class FPDemo {
public static void main(String[] args) { Optional<Integer> result = Stream.of(“f”, “ba”, “hello”) .map(s -> s.length()) .filter(l -> l <= 3) .max((o1, o2) -> o1-o2); System.out.println(result.get()); // 输出2 } } |
这段代码的的作用是从一组字符串数字中,过滤出长度小于等于3的字符串,并且求得其中的最大长度
Java为函数式编程提供了三个语法概念,Stream 类,Lambda表达式和函数式接口,Steam类,Lambda表达式和函数接口,Stream类通过支持”.”级联多个函数操作的代码的编写方式,引入了Lambda表达式简化了代码的编写,函数接口的作用就是可以将函数包裹成函数接口,来讲函数作为参数来使用
首先是Stream类
如果要计算一个表达式 (3-1) * 2+5,按照普通的函数调用写出来
add(multiply(substract(3,1),2),5)
比较容易读懂的方法可以写为
substract(3,1).multiply(2).add(5);
在Java中, “.”表示调用某个对象的方法,为了支持上面的级联调用方式,我们让每个函数都返回一个通用的类型,Stream类对象,在Stream类上的操作由两种,中间操作和终止操作,中间操作返回的是Stream对象,终止操作返回的是确定的值
下面就是一个简单的函数式编程的Demo
public class FPDemo {
public static void main(String[] args) { Optional<Integer> result = Stream.of(“f”, “ba”, “hello”) // of返回Stream<String>对象 .map(s -> s.length()) // map返回Stream<Integer>对象 .filter(l -> l <= 3) // filter返回Stream<Integer>对象 .max((o1, o2) -> o1-o2); // max终止操作:返回Optional<Integer> System.out.println(result.get()); // 输出2 } } |
我们看一下Lambda表达式
Java引入了Lambda表达式为了简化代码的编写,实际上,我们可以不用Lamdba表达式来书写例子上的代码,我们拿其中的map函数来举例说明
// Stream中map函数的定义:
public interface Stream<T> extends BaseStream<T, Stream<T>> { <R> Stream<R> map(Function<? super T, ? extends R> mapper); //…省略其他函数… } // Stream中map的使用方法: Stream.of(“fo”, “bar”, “hello”).map(new Function<String, Integer>() { @Override public Integer apply(String s) { return s.length(); } }); // 用Lambda表达式简化后的写法: Stream.of(“fo”, “bar”, “hello”).map(s -> s.length()); |
第一段代码展示了map函数的定义,实际上,map是一个Function接口(函数接口),第二段代码展示了map函数的使用方式,第三段代码就是针对第二段代码的简化开发,也就是所谓的语法糖,本质上就是第二段代码的写法
Lambda表达式包含三个部分, 输入 函数体 输出,表示出来的话就是下面这个样子
(a, b) -> { 语句1; 语句2;…; return 输出; } //a,b是输入参数
而且非常的灵活,如果输入参数只有一个,可以省略(),如果没有入参,那么可以直接将()和->都省略掉,如果没有返回值,可以省略return
Optional<Integer> result = Stream.of(“f”, “ba”, “hello”)
.map(s -> s.length()) .filter(l -> l <= 3) .max((o1, o2) -> o1-o2); // 还原为函数接口的实现方式 Optional<Integer> result2 = Stream.of(“fo”, “bar”, “hello”) .map(new Function<String, Integer>() { @Override public Integer apply(String s) { return s.length(); } }) .filter(new Predicate<Integer>() { @Override public boolean test(Integer l) { return l <= 3; } }) .max(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 – o2; } }); |
我们将上面的Lamdba表达式改为了函数实现,那么代码一瞬间就多了
最后一个组成的部分
上面的一段代码中的Function,Predicate,Comparator都是函数接口,C语言支持函数指针,可以将函数直接当变量来使用,Java没有函数指针这样的语法,所以可以通过函数接口,将函数包裹在接口中,当做变量来使用
函数接口就是接口,其中最重要的特性就是只能包含一个未实现的方法,Lamdba利用这个方法,来知道匹配哪个方法
@FunctionalInterface
public interface Function<T, R> { R apply(T t); // 只有这一个未实现的方法 default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } static <T> Function<T, T> identity() { return t -> t; } } @FunctionalInterface public interface Predicate<T> { boolean test(T t); // 只有这一个未实现的方法 default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } default Predicate<T> negate() { return (t) -> !test(t); } default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } } |
Guava对于函数式编程的增强
如果是Google Guava的设计师,对于Java函数的编程,能做什么增强呢?
可以增加Stream类的操作,加入例如 map, filter,max这样的操作补充,还是加入更多的函数接口
Function Predicate这样的函数接口,还是扩展一个新的例如stream类来支持级联呢?
实际上Google Guava并没有提供太多的函数式编程支持,仅仅封装了几个遍历集合的操作
Iterables.transform(Iterable, Function);
Iterators.transform(Iterator, Function); Collections.transfrom(Collection, Function); Lists.transform(List, Function); Maps.transformValues(Map, Function); Multimaps.transformValues(Mltimap, Function); … Iterables.filter(Iterable, Predicate); Iterators.filter(Iterator, Predicate); Collections2.filter(Collection, Predicate); … |
对于这个原因,是因为Guava认为使用函数式编程会导致代码可读性变差,不要过于滥用,所以没有太多的支持
本章重点:
函数式编程的简介,其是对于其他编程范式的补充,用于在一些特殊的领域发挥特殊的作用
对于函数式的编程,实际上不是很好理解,函数并不是一般编程上的函数概念,而是数学中的函数或者表达式的概念
具体到编程实现,函数式编程以无状态的函数作为组织代码的单元,函数的执行结果之和入参有关,和其他任何变量无关,同样的入参,无论怎么执行,结果一样
同样在Java,提供了三个语法支持函数式编程,Stream类,Lambda表达式,函数接口,Guava只是对于函数编程的一个重要应用场景,遍历进行了优化支持
其认为函数式编程可能会导致代码可读性变差
课后思考
对于函数式编程的优点和缺点,能说一下嘛?
优点:降低代码编写,提高编写效率,更加抽象.如果编写的好,复用性也很不错(因为无状态)
缺点:入门门槛不低,对于一些业务复杂的逻辑,有心而无力