Java函数式编程

函数

输入相同的情况下,无论多少次调用都会得到相同的输出。换言之,输入绝对决定输出。

为什么要有?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class Example {
    interface Lambda {
        int calculate (int a, int b);
    }
    // 对于add方法,位置固定,使用必须通过Example类
    int add (int a, int b) {
        return a + b;
    }
    // 对于add对象,位置可变,传递这条规则即可
    Lambda add = (a, b) -> a + b;
}

public class TestExample {
    public static void main(String[] args) {
        System.out.println(new Exmaple().add(3, 4));
    }
    System.out.println(new Example().add.calculate(1, 2));
}

关于位置可变与位置不可变,将设现在有3台JVM虚拟机,分别为Server,Client0和Client1。

假设Server没有存储运算逻辑,并随机生成两个数字进行运算。Client需要传递运算逻辑。Server虚拟机只有运算的接口定义。

此时,方式2就可以只传递运算方式的Lambda运算方式对象。而方式1需要完整的传递定义运算方法的类,并且服务端反序列化该类后实现运算逻辑,这前提是服务端有该类的字节码文件,相当于把实现绑定在了服务器端,失去了意义。

究其原因,Java底层实现了接口定义,因此可以直接传输Lambda对象,而传自定义对象就需要相对应的字节码文件。

函数对象的核心:

  • 行为参数化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
interface Lambda {
    boolean test(User user);
}
public List<User> filter(List<User> users, Lambda lambda) {
    List<User> res = new ArrayList<>();
    for (User user : users) {
        if (lambda.test(user)) {
            res.add(user);
        }
    }
    return res;
}
System.out.println(users, u -> u.age < 18);

函数编程语法

表现形式

  • Lambda表达式 (int a, int b) -> a + b;看作一个函数对象。
  • 方法引用,写法更简洁 Math::max 等价于(int a, int b) -> Math.max(a, b);

对象类型

两个维度

  • 参数个数类型相同
  • 返回值类型相同

两个维度一致便属于同一种类型。

1
2
3
4
5
6
7
8
9
public class Example2 {
    // 参数个数类型相同,返回值相同,都可以赋值给Type类型变量
    Type obj1 = (int a) -> (a & 1) == 0;
    Type obj2 = (int a) -> a > 0;
    @FunctionalInterface
    interface Type {
        boolean op(int a);
    }
}

函数式接口,仅包含一个抽象方法,用@FunctionalInterface检查。

JDK提供的函数式接口

  • IntPredicate断言型 1个int入参,1个boolean出参
  • IntBinaryOperator2个int入参,1个int出参
  • Supplier<T> 无入参,泛型出参
  • Function 泛型入参,泛型出参

其他常见函数式接口

  • Runnable()->void
  • Callable ()->T
  • Comparator(T,T)->int
  • Consumer(T)->void
  • Function(T)->R
  • Predicate(T)->boolean
  • Supplier()->T
  • BinaryOperator(T)->T

命名规则:

  • Consumer有参,无返回值
  • Function有参,有返回值
  • Predicate有参,返回布尔类型
  • Supplier无参,有返回值
  • Operator有参,有返回值,且类型一致

方法引用

将现有方法的调用化为函数对象

  • 静态方法
1
2
(String s) -> Integer.parseInt(s);
Integer::parseInt
  • 非静态方法
1
2
(stu) -> stu.getName();
Student::getName
  • 构造方法
1
2
() -> new Student();
Student::new

闭包「Closure」和柯里化「Currying」

  • 闭包
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class Example {
    @FunctionalInterface
    interface Lambda {
        int op(int y);
    }
    static void cal(Lambda lambda) {
        System.out.println(lambda.op(1));
    }
    public static void main(String[] args) {
        /**
        抑或是effective final
        */
        // 闭包是一种给函数执行提供数据的手段
        final int x = 10; // 由于函数要保证不变性,要加关键词final
        cal((int y) -> x + y); // 用到外界变量,形成闭包
    }
}

举例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class Example {
    public static void main(String[] args) {
        List<Runnable> list = new ArrayList<>();
        // 创建-10-个任务,并且给每个任务添加编号
        // 此处闭包作用:提供了函数对象参数以外数据,此处为线程序号
        for (int i = 0; i < 10; i++) {
            // 此处即是闭包,i不可变,这样写会报错
            // Runnable task = () -> System.out.println("执行任务" + (i + 1));
            int k = i + 1;
            Runnable task = () -> System.out.println(Thread.currentThread() + "执行任务" + k);
            list.add(task);
        }
        ExecutorService service = Exectors.newVirtualThreadExectuor();
        for (Runnable task : list) {
            service.submit(task);
        }
        // 阻塞主线程
        System.in.read();
    }
}
  • 柯里化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Example {
    @FunctionalInterface
    interface func {
        int op(int a, int b);
    }
    @FunctionalInterface
    interface funcA {
        funcB op(int a);
    }
    @FunctionalInterface
    interface funcB {
        int op(int b);
    }
    public static void main(String[] args) {
        func func1 = (a, b) -> a + b;
        func1.op(10, 20);
        // 需求:只传一个参数达到相同的效果
        // funca传入一个参数得到一个函数对象funcb
        // 多个参数的函数对象转为一系列只有一个参数的函数对象,这个过程称为柯里化
        // 柯里化的作用:函数分步执行
        funcA funca = (a) -> (b) -> a + b; // a相当于闭包的外界参数 (a) ->[b -> (a + b)]
        funcB funcb = funca.op(10);
        funcb.op(20);
    }
}

高阶函数

所谓高阶,就是它是其他函数对象的使用者。

作用:将通用、复杂的逻辑隐藏在高阶函数内,将易变、未定的逻辑放在外部的函数对象中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Example {
    public static void main(String[] args) {
        List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7);
        // 需求:逆序遍历集合,只负责处理,不改变集合。
        highOrder(list, System.out::println);
    }
    // highOrder就是一个高阶函数
    public static <T> void highOrder(List<T> list, Consumer<Integer> consumer) {
        ListIterator<T> iterator = list.listIterator(list.size());
        while (iterator.hasPrevios()) {
            T val = iterator.previous();
            consumer.accept(value);
        }
    }
}

Stream API

手写一个简单流玩耍一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class SimpleStream {
    public static void main(String[] args) {
        HashSet<Integer> ans = Simplestream.of(1, 2, 3, 4, 5, 6, 7)
            .collect(HashSet::new, HashSet::add);
    }
    
    public <C> C collect(Supplier<C> supplier, BiConsumer<C, T> consumer) {
        C c = new supplier.get(); // 创建容器
        for (T t : collection) {
            consumer.accept(c, t);
        }
        return c;
    }
    public SimpleStream<T> filter(Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T t : collection) {
            result.add(t);
        }
        return new SimpleStream<>(result);
    }
    private Collection<T> collection;
    public static <T> SimpleStream of(Collection<T> collection) {
        return new SimpleStream(collection);
    }
    private SimpleStream(Collection<T> collection) {
        this.collection = collection;
    }
}
  • filter 过滤
  • flatMap 降维打击

构建流的方式

  • 集合构建 集合.stream()
  • 数组构建 Arrays.stream(数组)
  • 对象构建 Stream.of(对象)

合并与截取

  • concat 合并
  • skip limit takeWhile dropWhile截取

查找与判断

  • filter(Predicate p).findAny()

  • filter(Predicate p).findFirst()

  • anyMatch(Predicate p)

  • allMatch(Predicate p)

  • noneMatch(Predicate p)

1
stream.filter(x -> (x & 1) == 0)).findFirst().ifPresent(System.out::println);

去重和排序

  • distinct
  • sorted
1
stream.sorted(Comparator.comparingInt(Student::age).reversed().thenComparingInt(stu -> stu.name().length()));

化简

  • reduce

收集

  • collect

流的特性

  • 一次使用,流中每个元素只能操作一次,不能重复执行
  • 两类操作,中间操作lazy、终结操作eager

并行流

  • parallel 转换为并行流
  • 两种方案,都是线程安全的,方案1更加节省内存空间
    • Characteristics.CONCURRENT + Characteristics.UNORDERED + 线程安全容器
    • 默认 + 线程不安全容器

举个栗子

异步处理:

1
2
3
CompletableFuture
	.supplyAsync(() -> someAwesomeFunc())
	.thenAccept(a -> System.out::println);

实现原理

Lambda表达式是一种语法糖,它会被翻译成类、对象、方法。

编译器发现Lambda表示式后,就会在当前类生成private static方法,方法内包含的就是lambda表示的逻辑。

类和对象运行时动态生成。

1
2
3
4
5
6
7
8
9
final class MyLambda implements BinaryOperator<Integer> {
    @Override
    public Integer apply(Integer a, Integer b) {
        return lambda$main$0(a, b);
    }
}
private static Integer lambda$main$0(Integer a, Integer b) {
    return a + b;
}
updatedupdated2024-10-062024-10-06