函数式编程(Functional Programming): 函数式编程是种编程范式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。
简而言之,用数学函数的思想编写代码,一个函数即为一个映射关系,每一个输入值有确定的、唯一的输出值。
函数与其他数据类型一样,可以用于赋值给其他变量,也可以作为参数传入一个函数中(相当于复合函数),还可以作为一个函数的返回值。
参数多态函数的参数既可以是数据类型,也可以是一个函数,对于 y=f(x) ,参数x既可以是一个值,也可以是另一个函数 x=g(z) 。
闭包函数内部引用的所有变量,其作用域局限于函数内部,闭包内部和外部没有任何关系影响。换句话说,函数内部描述的是怎么去做,整个过程处于一个黑盒中。
高阶函数对函数的嵌套使用,即复合函数,便是高阶函数,常用的一些对集合操作的已封装高阶函数例如:sort,map,reduce,filter等。
匿名函数匿名函数即lambda表达式,它是一种语法糖,对于FP,无需关心每一个函数叫什么名字,只需要知道它用于做什么,匿名便起到对代码结构的简化。
惰性计算函数的计算不是发生在定义时,而是发生在函数调用时,即发生在函数接收到实参之后。
递归通过形如链式调用的方式连续调用多个函数,每次调用的输出值作为下一个函数的输入值,相当于多层嵌套的复合函数。
柯里化把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数(相当于复合函数化简)。
无副作用函数内部与外界完全独立,所有功能只是返回一个值,而不影响外部变量的值。
引用透明函数输出值只取决于输入值,而不受外部任何变量影响。因此可以从函数内部实现推断函数行为。
通过函数复用,提升代码复用率。利用lambda语法糖,进一步简化代码结构。
易于理解模块化程度高,输出结果具有明确性,能根据输入值推断输出结果,便于验证函数正确性。
易于并发编程函数内部计算过程不影响外部变量,不存在线程问题,因此是并发安全的。
Java 8是java版本迭代中非常重要的一个版本,新增了很多重要的功能和api,本文重点讲述以下几个内容:函数式接口、Lambda语法糖、集合操作的Stream接口、对象操作的Optional类。
实现一个函数式接口需要满足以下条件:
接口需要使用FunctionalInterface注解:使用该注解后,接口中如果声明超过一个的抽象方法时,会报错,实际开发中,往往会省略该注解接口只包含一个抽象方法,可以包含多个default方法:这样便可以使用lambda语法简化代码结构示例:
@FunctionalInterface public interface Callback { void onProcess(Object data); dufault void onBefore(Object data){ } dufault void onAfter(Object data){ } }Consumer即消费者,是Java 8新增的函数式接口,它接收一个入参,执行操作,无返回值,示例:
Consumer<String> test = str -> Log.d("Test", str);andThen方法用于拼接Consumer执行,其内部默认实现本质上是构造了一个复合函数,内层为当前函数,外层为拼接的下一个函数,其运行时的效果等价为:先执行自身的accept,再执行拼接的accept,示例:
public class Demo { private static final String TAG = "Demo"; private Consumer<String> getUp = persoon -> Log.d(TAG, persoon + " get up"); private Consumer<String> work = persoon -> Log.d(TAG, persoon + " work"); private Consumer<String> eat = persoon -> Log.d(TAG, persoon + " eat"); private Consumer<String> goHome = persoon -> Log.d(TAG, persoon + " go home"); private Consumer<String> sleep = persoon -> Log.d(TAG, persoon + " sleep"); private void test() { getUp.andThen(work).andThen(eat).andThen(goHome).andThen(sleep).accept("Programmer"); } public static void main(String[] args) { new Demo().test(); } }输出结果:
Demo, Programmer get up Demo, Programmer work Demo, Programmer eat Demo, Programmer go home Demo, Programmer sleep如果我们希望得到函数的结果,那么就可以使用函数Function这个接口,顾名思义,这个接口就是起到函数y=f(x)的效果。接口声明定义的泛型有两个,T是入参的Type,而R指的是返回值Return。
apply这个方法,接受一个入参,经过内部函数变换,返回一个结果。示例:
Function<Integer, String> test = i -> { return String.valueOf(i); };或使用Lambda简化为:
Function<Integer, String> test = i -> String.valueOf(i);andThen和Consumer类似,构造了一个复合函数,先执行自身的apply,再执行拼接的apply,示例:
public class Demo { private static final String TAG = "Demo"; private Function<Integer, Integer> increase = i -> i + 1; private Function<Integer, Integer> square = i -> i * i; private Function<Integer, Integer> triple = i -> i * 3; private void test() { int result = increase.andThen(square).andThen(triple).apply(2); Log.d(TAG, "result = " + result); } public static void main(String[] args) { new Demo().test(); } }输出结果:
Demo, result = 27compose和andThen的执行顺序相反,先执行拼接的apply,再执行自身的apply,示例:
public class Demo { private static final String TAG = "Demo"; private Function<Integer, Integer> increase = i -> i + 1; private Function<Integer, Integer> square = i -> i * i; private Function<Integer, Integer> triple = i -> i * 3; private void test() { int result = increase.compose(square).compose(triple).apply(2); Log.d(TAG, "result = " + result); } public static void main(String[] args) { new Demo().test(); } }输出结果:
Demo, result = 37identity用于得到复合函数整体,即:返回y=f(g(h(x))),示例:
public class Demo { private static final String TAG = "Demo"; private Function<Integer, Integer> increase = i -> i + 1; private Function<Integer, Integer> square = i -> i * i; private Function<Integer, Integer> triple = i -> i * 3; private void test() { Function<Integer, Object> compose = Function.identity().compose(increase).compose(square).compose(triple); Log.d(TAG, "result = " + compose.apply(5)); } public static void main(String[] args) { new Demo().test(); } }输出结果:
Demo, result = 226断言接口,用于条件处理,它的方法从命名上便能理解用途:test直接返回判断结果,and相当于条件与,or相当于条件或,negate是条件非,isEqual判等,示例:
package demo; import java.util.function.Predicate; public class Demo { private static final String TAG = "Demo"; private Predicate<Programmer> isOldProgrammer = programmer -> programmer.age >= 35; private Predicate<Programmer> isMaleProgrammer = programmer -> programmer.gender == Gender.MALE; private Predicate<Programmer> isSoftwareEngineer = programmer -> programmer.position.equals(Position.SOFTWARE_ENGINEER); private void test() { Programmer programmer = new Programmer(23, Gender.MALE, Position.SOFTWARE_ENGINEER); Log.d(TAG, "is NOT old programmer: " + isOldProgrammer.negate().test(programmer)); Log.d(TAG, "is male software engineer: " + isMaleProgrammer.and(isSoftwareEngineer).test(programmer)); } public static void main(String[] args) { new Demo().test(); } public static class Programmer { public final int age; public final int gender; public final String position; public Programmer(int age, @Gender int gender, @Position String position) { this.age = age; this.gender = gender; this.position = position; } } public @interface Gender { int FEMALE = 0; int MALE = 1; } public @interface Position { String SOFTWARE_ENGINEER = "software_engineer"; String HARDWARE_ENGINEER = "hardware_engineer"; } }输出结果:
Demo, is NOT an old programmer: true Demo, is a male software engineer: trueLambda表达式是基于数学中的λ演算得来,本质上是一个匿名函数,只有符合函数式接口必要条件(只有一个抽象方法)的接口才能改写为Lambda表达式。
在格式上,它具有以下几个要求:
参数不声明类型如果只有一个参数,可以省略小括号,否则需要用括号将参数括起来如果函数主体只有一个语句,可以省略大括号,否则需要用括号将函数主体括起来如果函数主体是一个有返回值的语句,则可以省略return,否则需要明确表示返回值示例:
public class Demo { private static final String TAG = "Demo"; Consumer<String> origin1 = new Consumer<String>() { @Override public void accept(String str) { Log.d(TAG, str); }; }; Consumer<String> lambda1 = str -> Log.d(TAG, str); Runnable origin2 = new Runnable() { @Override public void run() { Log.d(TAG, "no param"); } }; Runnable lambda2 = () -> Log.d(TAG, "no param"); Function<Integer, String> origin3 = new Function<Integer, String>() { @Override public String apply(Integer i) { return String.valueOf(i); }; }; Function<Integer, String> lambda3 = i -> String.valueOf(i); }Stream接口定义了一系列集合操作的高阶函数的抽象方法,Collection接口中的stream方法和parallelStream获取其实例,而集合类直接或间接地实现了Collection接口,从而支持这些高阶函数。
区别在于,stream返回的是一个普通流,parallelStream返回的是一个并行流,后者在一定程度上能在并行下提升性能,但如果使用场景不当,则会降低性能。
API介绍:
public interface Stream<T> extends BaseStream<T, Stream<T>> { /** * 按条件进行过滤,返回过滤后的流。 */ Stream<T> filter(Predicate<? super T> predicate); /** * 传入一个映射函数,将旧的流中的每一个元素转换为新的元素,返回新的流。 */ <R> Stream<R> map(Function<? super T, ? extends R> mapper); /** * 传入一个映射函数,将旧的流中的每一个元素转换为整型,返回整型流。 */ IntStream mapToInt(ToIntFunction<? super T> mapper); /** * 传入一个映射函数,将旧的流中的每一个元素转换为长整型,返回长整型流。 */ LongStream mapToLong(ToLongFunction<? super T> mapper); /** * 传入一个映射函数,将旧的流中的每一个元素转换为双精度浮点型,返回双精度浮点型流。 */ DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper); /** * 如果旧的流中的元素本身也是集合,相当于原始数据是二维集合,该方法把旧的流中的每一个元素集合中的子元素, * 转换为新的元素,最后全部汇总为一个新的流,即:将二维集合映射为一维集合。 */ <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper); /** * 去除重复元素,内部实现是通过元素的equals方法,返回新的流。 */ Stream<T> distinct(); /** * 对元素进行排序,返回新的流。 * 只有元素本身实现了Comparable接口,才能调用该方法,否则会抛出异常。 */ Stream<T> sorted(); /** * 传入一个比较器,对元素进行排序,返回新的流。 */ Stream<T> sorted(Comparator<? super T> comparator); /** * 遍历每一个元素,进行操作,并返回流。 * 和forEach的区别在于,forEach是一个终端方法,无返回值,所以不能接着调用其他方法。 */ Stream<T> peek(Consumer<? super T> action); /** * 返回前maxSize个元素组成的新的流 */ Stream<T> limit(long maxSize); /** * 忽略前n个元素,返回剩下元素组成的新的流 */ Stream<T> skip(long n); /** * 遍历每一个元素,进行操作,无返回值,属于终端方法,如需后续调用,应该使用peek */ void forEach(Consumer<? super T> action); /** * 按顺序地遍地每一个元素并进行操作,和forEach的区别在于,如果流是并行流,那么forEach无法保证顺序, * 而forEachOrdered要求顺序,就不应该在并行流时使用,否则会有严重性能问题 */ void forEachOrdered(Consumer<? super T> action); /** * 返回流中的元素组成的数组 */ Object[] toArray(); /** * 传入一个初始值和二元操作函数,用于计算流中的元素得到一个最终结果。 */ T reduce(T identity, BinaryOperator<T> accumulator); /** * 只传入二元操作函数,因此可能无法执行得到结果,所以返回的是Optional对象。 */ Optional<T> reduce(BinaryOperator<T> accumulator); /** * 将流中的元素按传入的收集器的规则进行收集。 */ <R, A> R collect(Collector<? super T, A, R> collector); /** * 按照比较器的规则,返回流中元素最小值,可能没有最小值,因此返回Optional对象。 */ Optional<T> min(Comparator<? super T> comparator); /** * 按照比较器的规则,返回流中元素最大值,可能没有最大值,因此返回Optional对象。 */ Optional<T> max(Comparator<? super T> comparator); /** * 返回流中元素个数。 */ long count(); /** * 判断流中是否有任何元素满足条件。 */ boolean anyMatch(Predicate<? super T> predicate); /** * 判断流中是否所有元素都满足条件。 */ boolean allMatch(Predicate<? super T> predicate); /** * 判断流中是否没有元素满足条件。 */ boolean noneMatch(Predicate<? super T> predicate); /** * 返回流中第一个元素的Optional对象。 */ Optional<T> findFirst(); /** * 返回流中任何一个元素的Optional对象,普通流返回的是第一个,相当于findFirst, * 而并行流返回的可能是任何一个。 */ Optional<T> findAny(); /** * 创建一个没有元素的空的流。 */ public static<T> Stream<T> empty() { // TODO } /** * 创建只有一个元素的流。 */ public static<T> Stream<T> of(T t) { // TODO } /** * 将同一类的多个对象创建为流。 */ public static<T> Stream<T> of(T... values) { return Arrays.stream(values); } /** * 传入一个初始值和一元操作函数,将计算结果再次调用函数得到新的计算结果,不断迭代,返回所有的计算结果组成的流。 */ public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) { // TODO } /** * 传入一个无参函数,返回函数生成的值组成的流。 */ public static<T> Stream<T> generate(Supplier<T> s) { // TODO } /** * 将两个同泛型的流拼接成一个流。 */ public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) { // TODO } }由于方法太多,且方法名能直观反映其含义,所以不再一一举例说明。下面简单提一下并行流的注意事项:
在方法实现涉及顺序时,应该避免使用并行流,例如reduce,limit,skip,findFirst等非顺序存储数据结构的集合,应该避免使用并行流,例如LinkedList,LinkedHashMap,LinkedHashSet等Optional是一个包装类(类似基本类型包装类、引用类型包装类),旨在减少代码中出现空指针异常,适当地使用该类,可以避免泛滥的判空代码,提升整体可读性和美观性。
源码分析:
public final class Optional<T> { /** * 默认的空值的Optional对象,其value为null。 */ private static final Optional<?> EMPTY = new Optional<>(); private final T value; /** * 私有无参构造方法,创建一个空值的Optional对象。 */ private Optional() { this.value = null; } /** * 返回一个空值的Optional对象。 */ public static<T> Optional<T> empty() { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; } /** * 私有构造方法,如果值为空,会抛出NPE。 */ private Optional(T value) { this.value = Objects.requireNonNull(value); } /** * 由一个给定值创建Optional对象。 */ public static <T> Optional<T> of(T value) { return new Optional<>(value); } /** * 由一个可能为空的给定值创建Optional对象,如果值为空,则返回空值的Optional对象。 */ public static <T> Optional<T> ofNullable(T value) { return value == null ? empty() : of(value); } /** * 获取Optional对象的值,应该在确保值不为空的情况下调用该方法。 */ public T get() { if (value == null) { throw new NoSuchElementException("No value present"); } return value; } /** * 判断值是否为空。 */ public boolean isPresent() { return value != null; } /** * 传入一个Consumer,仅在值不为空的情况下执行。 */ public void ifPresent(Consumer<? super T> consumer) { if (value != null) consumer.accept(value); } /** * 传入一个Predicate,如果值为空,则直接返回自身, * 否则返回条件过滤后的Optional对象(不符合条件时返回的是值为空的Optional对象)。 */ public Optional<T> filter(Predicate<? super T> predicate) { Objects.requireNonNull(predicate); if (!isPresent()) return this; else return predicate.test(value) ? this : empty(); } /** * 传入一个映射函数,如果值为空,则值为空的Optional对象, * 否则返回映射后的值构造的Optional对象。 */ public<U> Optional<U> map(Function<? super T, ? extends U> mapper) { Objects.requireNonNull(mapper); if (!isPresent()) return empty(); else { return Optional.ofNullable(mapper.apply(value)); } } /** * 和map不同之处在于,映射函数限定了映射后也是一个Optional对象。 */ public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) { Objects.requireNonNull(mapper); if (!isPresent()) return empty(); else { return Objects.requireNonNull(mapper.apply(value)); } } /** * 传入一个备用值,如果当前值为空,则返回备用值。 */ public T orElse(T other) { return value != null ? value : other; } /** * 和orElse作用完全一样,区别在于,如果入参是一个方法,那么orElse一定会执行, * 而orElseGet根据惰性计算这个特性,仅在发生时才执行,即仅在当前值为空时才执行入参的方法。 */ public T orElseGet(Supplier<? extends T> other) { return value != null ? value : other.get(); } /** * 当前值为空时会抛出异常。 */ public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X { if (value != null) { return value; } else { throw exceptionSupplier.get(); } } }Optional类将判空相关代码封装在内部,调用者只需关注非空情况下的业务代码,甚至结合链式调用,优化代码结构。
函数式编程作为热门的、主流的编程范式之一,在很久以前便已经出现在其他编程语言中,java较晚地支持这一能力,但是其带来的好处是非常明显且巨大的。对于java语言使用者,应当掌握这一新特性,并加以利用,以达到提升代码质量的目的。