0%

Java 函数式编程

[TOC]

Java 函数式编程

一、函数式接口

1. @FunctionalInterface注解

@FunctionalInterface注解:作用于接口,表示该接口是函数式接口(functional interface)。

函数式接口有以下几个特点:

  1. 函数式接口有且只有一个抽象方法

  2. 默认方法不计入抽象方法的个数,静态方法也不计入抽象方法个数

  3. 覆盖java.lang.Object的方法的方法也不计入抽象方法的个数

  4. 函数式接口可以通过lambda表达式、方法引用、构造器引用来创建实例

  5. 对于编译器来说,只要一个接口符合函数式接口的定义就会将其当作函数式接口,不一定需要@FunctionalInterface注解标注,注解用于检查该接口是否只包含一个抽象方法

代码示例如下:

1
@FunctionalInterface
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
    @Override
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
@FunctionalInterface
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
@FunctionalInterface
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,即改变数据。常见的例子有:

  1. Iterable接口的forEach方法接受一个Comsumer接口类型的操作,对集合中的元素进行操作,例如打印等。

3. Function接口

1
@FunctionalInterface
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
@FunctionalInterface
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
@Override
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
}

参考文献