[TOC]
Java 函数式编程
一、函数式接口
1. @FunctionalInterface注解
@FunctionalInterface注解:作用于接口,表示该接口是函数式接口(functional interface)。
函数式接口有以下几个特点:
函数式接口有且只有一个抽象方法
默认方法不计入抽象方法的个数,静态方法也不计入抽象方法个数
覆盖java.lang.Object的方法的方法也不计入抽象方法的个数
函数式接口可以通过lambda表达式、方法引用、构造器引用来创建实例
对于编译器来说,只要一个接口符合函数式接口的定义就会将其当作函数式接口,不一定需要@FunctionalInterface注解标注,注解用于检查该接口是否只包含一个抽象方法
代码示例如下:
1 |
|
2 | public interface HelloFuncInterface { |
3 | |
4 | String hello(); |
5 | |
6 | default String helloWorld() { |
7 | return "hello world"; |
8 | } |
9 | |
10 | static void printHello() { |
11 | System.out.println("Hello"); |
12 | } |
13 | } |
2. JDK中的函数式接口
java.lang.Runnable
java.awt.event.ActionListener
java.util.Comparator
java.util.concurrent.Callable
java.util.function包下的接口,如Consumer、Predicate、Supplier等
二、lambda表达式
lambda表达式用来实现函数式接口。
1. lambda表达式形式
(params) -> expression
(params) -> statement
(params) -> { statements }
1 | /** |
2 | * 通过lambda表达式实现自定义的函数式接口(注:JAVA 8 之前一般是用匿名类实现的) |
3 | */ |
4 | private void hello() { |
5 | HelloFuncInterface helloFuncInterface = () -> { |
6 | System.out.println("hello"); |
7 | return "hello"; |
8 | }; |
9 | helloFuncInterface.hello(); |
10 | } |
2. lambda表达式替换匿名类,减少冗余代码
1 | new Thread(() -> { |
2 | System.out.println(Thread.currentThread().getName() + ":runnable by lambda expression"); |
3 | }).start(); |
4 | |
5 | new Thread(new Runnable() { |
6 | |
7 | public void run() { |
8 | System.out.println(Thread.currentThread().getName() + ":runnable by anonymous class"); |
9 | } |
10 | }).start(); |
三、方法引用
方法引用由::双冒号操作符标示,看起来像C++的作用域解析运算符。实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!至于返回值就不作要求。
1 | /** |
2 | * 方法引用 |
3 | */ |
4 | private void methodReference() { |
5 | List<String> pets = Arrays.asList("cat", "dog", "snake", "squirrel"); |
6 | pets.forEach((a) -> System.out.println(a));//lambda表达式 |
7 | pets.forEach(System.out::println);//方法引用 |
8 | } |
1. 引用方法^1
- 对象引用::实例方法名
System.out是一个静态成员变量对象
1 | Consumer<String> consumer = System.out::println; |
2 | consumer.accept("hello"); |
- 类名::静态方法名
1 | Function<Long, Long> abs = Math::abs; |
2 | System.out.println(abs.apply(-3L)); |
- 类名::实例方法名
1 | BiPredicate<String, String> b = String::equals; |
2 | System.out.println(b.test("abc", "abcd")); |
2. 引用构造器
在引用构造器的时候,构造器参数列表要与接口中抽象方法的参数列表一致,格式为 类名::new。如
1 | Function<Integer, StringBuffer> fun = StringBuffer::new; |
2 | StringBuffer buffer = fun.apply(10); |
Function接口的apply方法接收一个参数,并且有返回值。在这里接收的参数是Integer类型,与StringBuffer类的一个构造方法StringBuffer(int capacity)对应,而返回值就是StringBuffer类型。上面这段代码的功能就是创建一个Function实例,并把它apply方法实现为创建一个指定初始大小的StringBuffer对象。
3. 引用数组
引用数组和引用构造器很像,格式为 类型[]::new,其中类型可以为基本类型也可以是类。如
1 | Function<Integer, int[]> fun = int[]::new; |
2 | int[] arr = fun.apply(10); |
3 | |
4 | Function<Integer, Integer[]> fun2 = Integer[]::new; |
5 | Integer[] arr2 = fun2.apply(10); |
四、java.util.function
1. Predicate接口
1 |
|
2 | public interface Predicate<T> { |
3 | |
4 | /** |
5 | * Evaluates this predicate on the given argument. |
6 | * |
7 | * @param t the input argument |
8 | * @return {@code true} if the input argument matches the predicate, |
9 | * otherwise {@code false} |
10 | */ |
11 | boolean test(T t); |
12 | } |
Predicate是一个函数式接口,也是一个泛型接口,作用是对传入的一个泛型参数进行判断,并返回一个boolean值,任何符合这一条件的lambda表达式都可以转为Predicate接口,该接口的实现一般用于集合过滤等。下面提供一些例子:
1 | /** |
2 | * lambda表达式加Predicate接口 |
3 | */ |
4 | private void predicateDemo() { |
5 | List<String> pets = Arrays.asList("cat", "dog", "snake", "squirrel"); |
6 | //简单的Predicate接口示例 |
7 | pets.stream().filter(s -> s.startsWith("s")).forEach(System.out::println); |
8 | |
9 | Predicate<String> startWith = s -> s.startsWith("s"); |
10 | Predicate<String> lengthPredicate = s -> s.length() > 2; |
11 | //Predicate支持and()操作将两个Predicate接口的逻辑取与 |
12 | System.out.println("Predicate and() demo:"); |
13 | pets.stream().filter(startWith.and(lengthPredicate)).forEach(System.out::println); |
14 | //Predicate支持or()操作将两个Predicate接口的逻辑取或 |
15 | System.out.println("Predicate or() demo:"); |
16 | pets.stream().filter(startWith.or(lengthPredicate)).forEach(System.out::println); |
17 | //Predicate支持negate()操作将Predicate接口的逻辑取反 |
18 | System.out.println("Predicate negate() demo:"); |
19 | pets.stream().filter(startWith.negate()).forEach(System.out::println); |
20 | //Predicate静态方法isEqual() |
21 | System.out.println("Predicate isEqual() demo:"); |
22 | pets.stream().filter(Predicate.isEqual("cat")).forEach(System.out::println); |
23 | } |
从示例中可以看到,Predicate接口支持and(), or(), negate()等,并且有一个静态方法isEqual。
2. Consumer接口
1 |
|
2 | public interface Consumer<T> { |
3 | |
4 | /** |
5 | * Performs this operation on the given argument. |
6 | * |
7 | * @param t the input argument |
8 | */ |
9 | void accept(T t); |
10 | |
11 | /** |
12 | * Returns a composed {@code Consumer} that performs, in sequence, this |
13 | * operation followed by the {@code after} operation. If performing either |
14 | * operation throws an exception, it is relayed to the caller of the |
15 | * composed operation. If performing this operation throws an exception, |
16 | * the {@code after} operation will not be performed. |
17 | * |
18 | * @param after the operation to perform after this operation |
19 | * @return a composed {@code Consumer} that performs in sequence this |
20 | * operation followed by the {@code after} operation |
21 | * @throws NullPointerException if {@code after} is null |
22 | */ |
23 | default Consumer<T> andThen(Consumer<? super T> after) { |
24 | Objects.requireNonNull(after); |
25 | return (T t) -> { accept(t); after.accept(t); }; |
26 | } |
27 | } |
Consumer是一个函数式接口,也是一个泛型接口,作用是对传入的一个泛型参数进行操作,可能会对原数据产生side effect,即改变数据。常见的例子有:
- Iterable接口的forEach方法接受一个Comsumer接口类型的操作,对集合中的元素进行操作,例如打印等。
3. Function接口
1 |
|
2 | public interface Function<T, R> { |
3 | |
4 | /** |
5 | * Applies this function to the given argument. |
6 | * |
7 | * @param t the function argument |
8 | * @return the function result |
9 | */ |
10 | R apply(T t); |
11 | } |
该接口代表接受一个参数并返回一个参数的操作类型。
4. Supplier接口
1 |
|
2 | public interface Supplier<T> { |
3 | |
4 | /** |
5 | * Gets a result. |
6 | * |
7 | * @return a result |
8 | */ |
9 | T get(); |
10 | } |
该接口表示调用后返回一个参数的操作类型,返回结果不要求一定是新创建的或者不同的。
五、StreamAPI原理
1. Collection接口的stream(), parallelStream()方法
Collection接口,即各种集合类的最上层的接口,支持通过stream(), parallelStream()方法产生Stream。源码如下:
1 | default Stream<E> stream() { |
2 | return StreamSupport.stream(spliterator(), false); |
3 | } |
4 | |
5 | default Stream<E> parallelStream() { |
6 | return StreamSupport.stream(spliterator(), true); |
7 | } |
其中spliterator()返回Spliterator接口,该接口用于对collection进行遍历或分片(parallelStream的情况下)。
2. Stream接口及其实现ReferencePipeline
Stream接口支持一系列操作,包括filter, map, distinct, sorted, peak, limit, skip等方法,这些方法仍然返回Stream接口,还支持reduce, forEach, min, max, collect, count, toArray等方法,这些方法不再返回Stream接口,是对流式操作的结束操作,是TerminalOp。
Stream接口的具体实现是ReferencePipeline类,执行具体的操作。具体可查看源码,此处不展开。
对于parallelStream不同的操作有不同的实现,各自的实现会决定是否真正并行操作。例如,reduce操作会调用ReduceOps执行具体操作,ReduceOps中ReduceOp类的部分源码如下:
1 |
|
2 | public <P_IN> R evaluateParallel(PipelineHelper<T> helper, |
3 | Spliterator<P_IN> spliterator) { |
4 | return new ReduceTask<>(this, helper, spliterator).invoke().get(); |
5 | } |
其中ReduceTask实现了ForkJoinTask,表明它是通过fork/join框架实现的并行处理。
参考文献【3】^3【4】^4中对StreamAPI原理有更详细的解释,这块后续我再更新。
3. generator 生成器
Stream为生成器的创建提供了便捷,生成器的好处是只有在你需要的时候才生成数据或对象,不需要提前生成。
例如,IntStream的generate()方法就生成一个流,可以无限调用生成整数。
1 | public static IntStream generate(IntSupplier s) { |
2 | Objects.requireNonNull(s); |
3 | return StreamSupport.intStream( |
4 | new StreamSpliterators.InfiniteSupplyingSpliterator.OfInt(Long.MAX_VALUE, s), false); |
5 | } |