函数式编程简介
函数式编程是一种编程范式,常见的编程范式还有命令式编程和逻辑式编程,其中命令式编程是对计算机硬件的抽象,就拿Java语言来时,变量对应存储单元、赋值语句对应获取和存储指令、表达式对应内存引用和算数运算指令、控制语句对应着跳转指令等。函数式编程是面向数学的抽象,这个函数不是计算机范畴的函数而是数学范畴的函数。关于逻辑式编程,这里不做介绍,感兴趣可查阅资料资料。
- 函数是“一等公民”,函数和其他参数一样,可以赋值给变量,可以作为参数传入另外一个函数,也可以作为别的函数的返回值;
- 只使用表达式,不使用语句,函数式编程开发的动机就是处理运算不考虑I/O,函数式编程要求把I/O限制到最小,保持运算过程的单纯性;
- 函数式编程没有“副作用”和不修改状态,函数保持独立,所有功能就是返回一个新的值,没有其他行为,不能修改外部变量的值;
- 引用透明,函数式编程中函数返回的结果只受传入参数的影响,不受外部状态变化的影响,就是只要传入相同的参数都会返回相同的结果。
- 代码简介,开发迅速;
- 接近自然语言,便于理解;
- 方便代码管理;
- 易于并发编程;
- 代码热升级等
Java函数式编程
JDK1.8对函数式编程进行了支持,引入了注解@FunctionalInterface来标注一个接口为函数式接口,函数式接口要求一个接口只能有一个抽象方法,但是可以有多个默认方法和静态方法。java.util.function包下提供了很多默认的函数式接口,一共有四十多个如下所示,这些接口大致可以分为四大类,分别是Function、Consumer、Supplier和Prediscate的扩展,这四个函数是整个函数式接口的基础。
@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;
}
}
Function接口是一个运算接口,传入一个参数,并返回一个返回值,该接口共定义了四个方法,一个抽象方法,两个默认方法和一个静态方法:
- apply(T t),执行Function定义的方法;
- Function compose(Function before) ,before的返回值作为当前Function的传入参数;
- Function andThen(Function after),当前Function的执行结果作为after的传入参数;
- identity(),将输入参数作为返回值。
//字符串拼接功能
Function<String, String> function1 = str -> "hello" + str;
//字符串截取
Function<String, String> function2 = (str) -> str.substring(1);
//返回"helloworld"
System.out.println(function1.apply("world"));
//返回"orld",字符串被截取
System.out.println(function2.apply("world"));
//返回"helloorld",先执行function2,再执行function1
System.out.println(function1.compose(function2).apply("world"));
//返回"elloworld",先执行function1,再执行function2
System.out.println(function1.andThen(function2).apply("world"));
//输出传入参数
System.out.println(Function.identity().apply("world"));
System.out.println(Function.identity().apply(new Object()));
输出结果
helloworld
orld
helloorld
elloworld
world
java.lang.Object@17f052a3
Consumer意思是消费者,该接口表示传入一个参数供消费,并且没有返回值,该接口中定义了两个方法,一个抽象方法和一个默认方法:
- void accept(T t),执行Consumer定义的消费逻辑;
- Consumer<T> andThen(Consumer<? super T> after),表示传入一个参数被消费了两次,当前Consumer先执行消费逻辑,然后after执行消费逻辑。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
通过下面的示例来了解一下Consumer的使用和特性
Consumer<Integer> addConsumer = param -> System.out.println(param + 100);
Consumer<Integer> consumer = param -> System.out.println(param - 50);
addConsumer.accept(50);
consumer.accept(50);
addConsumer.andThen(consumer).accept(50);
输出结果为
150
0
150
0
定义了两个Consumer分别进行+100和-50的操作并打印,最后一个输出结果我预测应该输出100,应为前面已经进行了+100的操作,但是返回结果是0,说明传入的参数被操作了两次但是没有进行赋值操作。
Supplier意为供应,与Consumer相反,Supplier无参数传入,但是有一个返回值,该接口比较简单,只定义了一个抽象方法get(),表示执行Supplier定义的逻辑返回一个对象。
@FunctionalInterface
public interface Supplier<T> {
T get();
}
通过下面的示例了解一下Supplier的定义和使用
Supplier<String> stringSupplier = () -> "hello world";
//打印hello world
System.out.println(stringSupplier.get());
定义了一个十分简单的Supplier,返回字符串“hello world”,通过get()方法可以获取。
Predicate意为预测,该接口接收一个参数,返回true或者false,接口定义了五个方法,其中包括一个抽象方法,三个默认方法和一个静态方法:
- test(T t),执行Predicate中定义的逻辑,返回true或者false;
- Predicate and(Predicate other),两个Predicate进行&&运算;
- negate(),当前Predicate执行的结果取反;
- Predicate or(Predicate other),两个Predicate进行||运算;
- isEqual
@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);
}
}
通过下面示例对Predicate的定义和使用进行简单介绍
Predicate<String> startWithPredicate = str -> str.startsWith("hello");
Predicate<String> endWithPredicate = str -> str.endsWith("world");
//true
System.out.println(startWithPredicate.test("hello world"));
//true
System.out.println(endWithPredicate.test("hello world"));
Predicate<String> andPredicate = startWithPredicate.and(endWithPredicate);
Predicate<String> orPredicate = startWithPredicate.or(endWithPredicate);
//false
System.out.println(andPredicate.test("hello java"));
//true
System.out.println(orPredicate.test("hello java"));
Predicate<String> oppPredicate = startWithPredicate.negate();
//false
System.out.println(oppPredicate.test("hello world"));
定义两个基础的Predicate,startWithPredicate判断字符串是否以“hello”开头,endWithPredicate判断字符串是否以“world”结尾,并对两个Predicate进行了"&&"、"||"以及取反操作,执行结果如上所示。
总结
本文对函数式编程进行了简单的介绍,对jdk1.8中四个最重要的函数式接口的定义和使用方法进行了详细的介绍,但是缺乏函数式编程的思想在实际项目中的应用,最近发现,虽然我最近开发的项目中通过lambda表达式使用了一些函数式接口,但是我觉得函数式接口应该是广泛使用在框架代码中的,最近在思考这个问题,后续分享。
网友评论