函数式编程,英文翻译为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只是对于函数编程的一个重要应用场景,遍历进行了优化支持

其认为函数式编程可能会导致代码可读性变差

课后思考

对于函数式编程的优点和缺点,能说一下嘛?

优点:降低代码编写,提高编写效率,更加抽象.如果编写的好,复用性也很不错(因为无状态)

缺点:入门门槛不低,对于一些业务复杂的逻辑,有心而无力

发表评论

邮箱地址不会被公开。 必填项已用*标注