函数式编程 stream流 lambda表达式

发布于:2025-06-15 ⋅ 阅读:(15) ⋅ 点赞:(0)

一. 函数

行为参数化

interface Lambda {
    boolean test(Student student);
}

方法可以统一成下述代码

static List<Student> filter(List<Student> students, Lambda lambda) {
    List<Student> result = new ArrayList<>();
    for (Student student : students) {
        if (lambda.test(student)) {
            result.add(student);
        }
    }
    return result;
}

好,最后怎么给它传递不同实现呢?

filter(students, student -> student.sex.equals("男"));

以及

filter(students, student -> student.age <= 18);

还有新需求也能满足

filter(students, student -> student.sex.equals("男") && student.age <= 18);

这样就实现了以不变应万变,而变换即是一个个函数对象,也可以称之为行为参数化

延迟执行

在记录日志时,假设日志级别是 INFO,debug 方法会遇到下面的问题:

  • 本不需要记录日志,但 expensive 方法仍被执行了
static Logger logger = LogManager.getLogger();

public static void main(String[] args) {
    System.out.println(logger.getLevel());
    logger.debug("{}", expensive());
}

static String expensive() {
    System.out.println("执行耗时操作");
    return "结果";
}

改进方法1:

if(logger.isDebugEnabled())
    logger.debug("{}", expensive());

显然这么做,很多类似代码都要加上这样 if 判断,很不优雅

改进方法2:

在 debug 方法外再套一个新方法,内部逻辑大概是这样:

public void debug(final String msg, final Supplier<?> lambda) {
    if (this.isDebugEnabled()) {
        this.debug(msg, lambda.get());
    }
}

调用时这样:

logger.debug("{}", () -> expensive());

expensive() 变成了不是立刻执行,在未来 if 条件成立时才执行

二. 函数编程语法

表现形式

在 Java 语言中,lambda 对象有两种形式:lambda 表达式与方法引用

lambda 对象的类型是由它的行为决定的,如果有一些 lambda 对象,它们的入参类型、返回值类型都一致,那么它们可以看作是同一类的 lambda 对象,它们的类型,用函数式接口来表示

常见函数式接口

在这里插入图片描述

在这里插入图片描述

六种方法引用

1)类名::静态方法名

如何理解:

  • 函数对象的逻辑部分是:调用此静态方法
  • 因此这个静态方法需要什么参数,函数对象也提供相应的参数即可
public class Type2Test {
    public static void main(String[] args) {
        /*
            需求:挑选出所有男性学生
         */
        Stream.of(
                        new Student("张无忌", "男"),
                        new Student("周芷若", "女"),
                        new Student("宋青书", "男")
                )
                .filter(Type2Test::isMale)
                .forEach(student -> System.out.println(student));
    }

    static boolean isMale(Student student) {
        return student.sex.equals("男");
    }

    record Student(String name, String sex) {
    }
}
  • filter 这个高阶函数接收的函数类型(Predicate)是:一个 T 类型的入参,一个 boolean 的返回值
    • 因此我们只需要给它提供一个相符合的 lambda 对象即可
  • isMale 这个静态方法有入参 Student 对应 T,有返回值 boolean 也能对应上,所以可以直接使用

输出

Student[name=张无忌, sex=男]
Student[name=宋青书, sex=男]

2)类名::非静态方法名

如何理解:

  • 函数对象的逻辑部分是:调用此非静态方法
  • 因此这个函数对象需要提供一个额外的对象参数,以便能够调用此非静态方法
  • 非静态方法的剩余参数,与函数对象的剩余参数一一对应

例1:

public class Type3Test {
    public static void main(String[] args) {
        highOrder(Student::hello);
    }

    static void highOrder(Type3 lambda) {
        System.out.println(lambda.transfer(new Student("张三"), "你好"));
    }

    interface Type3 {
        String transfer(Student stu, String message);
    }

    static class Student {
        String name;

        public Student(String name) {
            this.name = name;
        }

        public String hello(String message) {
            return this.name + " say: " + message;
        }
    }
}

上例中函数类型的

  • 参数1 对应着 hello 方法所属类型 Student
  • 参数2 对应着 hello 方法自己的参数 String
  • 返回值对应着 hello 方法自己的返回值 String

输出

张三 say: 你好

例2:改写之前根据性别过滤的需求

public class Type2Test {
    public static void main(String[] args) {
        /*
            需求:挑选出所有男性学生
         */
        Stream.of(
                        new Student("张无忌", "男"),
                        new Student("周芷若", "女"),
                        new Student("宋青书", "男")
                )
                .filter(Student::isMale)
                .forEach(student -> System.out.println(student));
    }

    record Student(String name, String sex) {
        boolean isMale() {
            return this.sex.equals("男");
        }
    }
}
  • filter 这个高阶函数接收的函数类型(Predicate)是:一个 T 类型的入参,一个 boolean 的返回值
    • 因此我们只需要给它提供一个相符合的 lambda 对象即可
  • 它的入参1 T 对应着 isMale 非静态方法的所属类型 Student
  • 它没有其它参数,isMale 方法也没有参数
  • 返回值都是 boolean

输出

Student[name=张无忌, sex=男]
Student[name=宋青书, sex=男]

例3:将学生对象仅保留学生的姓名

public class Type2Test {
    public static void main(String[] args) {
        Stream.of(
                        new Student("张无忌", "男"),
                        new Student("周芷若", "女"),
                        new Student("宋青书", "男")
                )
                .map(Student::name)
                .forEach(student -> System.out.println(student));
    }

    record Student(String name, String sex) {
        boolean isMale() {
            return this.sex.equals("男");
        }
    }
}
  • map 这个高阶函数接收的函数类型是(Function)是:一个 T 类型的参数,一个 R 类型的返回值
  • 它的入参1 T 对应着 name 非静态方法的所属类型 Student
  • 它没有剩余参数,name 方法也没有参数
  • 它的返回值 R 对应着 name 方法的返回值 String

输出

张无忌
周芷若
宋青书

3)对象::非静态方法名

如何理解:

  • 函数对象的逻辑部分是:调用此非静态方法
  • 因为对象已提供,所以不必作为函数对象参数的一部分
  • 非静态方法的剩余参数,与函数对象的剩余参数一一对应
public class Type4Test {
    public static void main(String[] args) {
        Util util = new Util(); // 对象
        Stream.of(
                        new Student("张无忌", "男"),
                        new Student("周芷若", "女"),
                        new Student("宋青书", "男")
                )
                .filter(util::isMale)
                .map(util::getName)
                .forEach(student -> System.out.println(student));
    }

    record Student(String name, String sex) {
        boolean isMale() {
            return this.sex.equals("男");
        }
    }

    static class Util {
        boolean isMale(Student student) {
            return student.sex.equals("男");
        }
        String getName(Student student) {
            return student.name();
        }
    }
}

其实较为典型的一个应用就是 System.out 对象中的非静态方法,最后的输出可以修改为

.forEach(System.out::println);

这是因为

  • forEach 这个高阶函数接收的函数类型(Consumer)是一个 T 类型参数,void 无返回值
  • 而 System.out 对象中有非静态方法 void println(Object x) 与之一致,因此可以将此方法化为 lambda 对象给 forEach 使用

4)类名::new

对于构造方法,也有专门的语法把它们转换为 lambda 对象

函数类型应满足

  • 参数部分与构造方法参数一致
  • 返回值类型与构造方法所在类一致

例如:

public class Type5Test {
    static class Student {
        private final String name;
        private final int age;

        public Student() {
            this.name = "某人";
            this.age = 18;
        }

        public Student(String name) {
            this.name = name;
            this.age = 18;
        }

        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }

        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

    interface Type51 {
        Student create();
    }

    interface Type52 {
        Student create(String name);
    }

    interface Type53 {
        Student create(String name, int age);
    }

    public static void main(String[] args) {
        hiOrder((Type51) Student::new);
        hiOrder((Type52) Student::new);
        hiOrder((Type53) Student::new);
    }

    static void hiOrder(Type51 creator) {
        System.out.println(creator.create());
    }

    static void hiOrder(Type52 creator) {
        System.out.println(creator.create("张三"));
    }

    static void hiOrder(Type53 creator) {
        System.out.println(creator.create("李四", 20));
    }
}

5)this::非静态方法名

算是形式2的特例,只能用在类内部

public class Type6Test {
    public static void main(String[] args) {
        Util util = new UtilExt();
        util.hiOrder(Stream.of(
                new Student("张无忌", "男"),
                new Student("周芷若", "女"),
                new Student("宋青书", "男")
        ));
    }

    record Student(String name, String sex) {

    }

    static class Util {
        boolean isMale(Student student) {
            return student.sex.equals("男");
        }

        boolean isFemale(Student student) {
            return student.sex.equals("女");
        }

        void hiOrder(Stream<Student> stream) {
            stream
                    .filter(this::isMale)
                    .forEach(System.out::println);
        }
    }
}

6)super::非静态方法名

算是形式2的特例,只能用在类内部(用在要用 super 区分重载方法时)

public class Type6Test {
	
    //...
    
    static class UtilExt extends Util {
        void hiOrder(Stream<Student> stream) {
            stream
                    .filter(super::isFemale)
                    .forEach(System.out::println);
        }
    }
}

7)特例

函数接口和方法引用之间,可以差一个返回值,例如

public class ExceptionTest {
    public static void main(String[] args) {
        Runnable task1 = ExceptionTest::print1;
        Runnable task2 = ExceptionTest::print2;
    }
    
    static void print1() {
        System.out.println("task1 running...");
    }

    static int print2() {
        System.out.println("task2 running...");
        return 1;
    }
}
  • 可以看到 Runnable 接口不需要返回值,而实际的函数对象多出的返回值也不影响使用

闭包(Closure)

何为闭包,闭包就是函数对象外界变量绑定在一起,形成的整体。例如

public class ClosureTest1 {
    interface Lambda {
        int add(int y);
    }
    
    public static void main(String[] args) {
        int x = 10;

        highOrder(y -> x + y);
    }

    static void highOrder(Lambda lambda) {
        System.out.println(lambda.add(20));
    }
}
  • 代码中的 y → x + y y \rightarrow x + y yx+y x = 10 x = 10 x=10,就形成了一个闭包
  • 可以想象成,函数对象有个背包,背包里可以装变量随身携带,将来函数对象甭管传递到多远的地方,包里总装着个 x = 10 x = 10 x=10
  • 有个限制,局部变量 x 必须是 final 或 effective final 的,effective final 意思就是,虽然没有用 final 修饰,但就像是用 final 修饰了一样,不能重新赋值,否则就语法错误。
    • 意味着闭包变量,在装进包里的那一刻,就不能变化了
    • 道理也简单,为了保证函数的不变性,防止破坏成道
  • 闭包是一种给函数执行提供数据的手段,函数执行既可以使用函数入参,还可以使用闭包变量

public class ClosureTest2 {

    // 闭包作用:给函数对象提供参数以外的数据
    public static void main(String[] args) throws IOException {
        // 创建 10 个任务对象,并且每个任务对象给一个任务编号
        List<Runnable> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int k = i + 1;
            Runnable task 
                = () -> System.out.println(Thread.currentThread()+":执行任务" + k);
            list.add(task);
        }

        ExecutorService service = Executors.newVirtualThreadPerTaskExecutor();
        for (Runnable task : list) {
            service.submit(task);
        }
        System.in.read();
    }
}

柯里化(Carrying)

柯里化的作用是让函数对象分步执行(本质上是利用多个函数对象和闭包)

例如:

public class Carrying1Test {
    public static void main(String[] args) {
        highOrder(a -> b -> a + b);
    }

    static void highOrder(Step1 step1) {
        Step2 step2 = step1.exec(10);
        System.out.println(step2.exec(20));
        System.out.println(step2.exec(50));
    }

    interface Step1 {
        Step2 exec(int a);
    }

    interface Step2 {
        int exec(int b);
    }
}

代码中

  • a → . . . a \rightarrow ... a... 是第一个函数对象,它的返回结果 b → . . . b \rightarrow ... b... 是第二个函数对象
  • 后者与前面的参数 a 构成了闭包
  • step1.exec(10) 确定了 a 的值是 10,返回第二个函数对象 step2,a 被放入了 step2 对象的背包记下来了
  • step2.exec(20) 确定了 b 的值是 20,此时可以执行 a + b 的操作,得到结果 30
  • step2.exec(50) 分析过程类似

高阶函数(Higher-Order Functions)

就是指使用lambda的函数

三. Stream API

过滤

record Fruit(String cname, String name, String category, String color) { }

Stream.of(
    new Fruit("草莓", "Strawberry", "浆果", "红色"),
    new Fruit("桑葚", "Mulberry", "浆果", "紫色"),
    new Fruit("杨梅", "Waxberry", "浆果", "红色"),
    new Fruit("核桃", "Walnut", "坚果", "棕色"),
    new Fruit("草莓", "Peanut", "坚果", "棕色"),
    new Fruit("蓝莓", "Blueberry", "浆果", "蓝色")
)

在这里插入图片描述

找到所有浆果

.filter(f -> f.category.equals("浆果"))

找到蓝色的浆果

方法1:

.filter(f -> f.category().equals("浆果") && f.color().equals("蓝色"))

方法2:让每个 lambda 只做一件事,两次 filter 相对于并且关系

.filter(f -> f.category.equals("浆果"))
.filter(f -> f.color().equals("蓝色"))

方法3:让每个 lambda 只做一件事,不过比方法2强的地方可以 or,and,nagate 运算

.filter(((Predicate<Fruit>) f -> f.category.equals("浆果")).and(f -> f.color().equals("蓝色")))

映射

在这里插入图片描述

.map(f -> f.cname() + "酱")

降维

Stream.of(
        List.of(
                new Fruit("草莓", "Strawberry", "浆果", "红色"),
                new Fruit("桑葚", "Mulberry", "浆果", "紫色"),
                new Fruit("杨梅", "Waxberry", "浆果", "红色"),
                new Fruit("蓝莓", "Blueberry", "浆果", "蓝色")
        ),
        List.of(
                new Fruit("核桃", "Walnut", "坚果", "棕色"),
                new Fruit("草莓", "Peanut", "坚果", "棕色")
        )
)
    
.flatMap(Collection::stream)    
  • 这样把坚果和浆果两个集合变成了含六个元素的水果流

例2:

Stream.of(
        new Order(1, List.of(
                new Item(6499, 1, "HUAWEI MateBook 14s"),
                new Item(6999, 1, "HUAWEI Mate 60 Pro"),
                new Item(1488, 1, "HUAWEI WATCH GT 4")
        )),
        new Order(1, List.of(
                new Item(8999, 1, "Apple MacBook Air 13"),
                new Item(7999, 1, "Apple iPhone 15 Pro"),
                new Item(2999, 1, "Apple Watch Series 9")
        ))
)

想逐一处理每个订单中的商品

.flatMap(order -> order.items().stream())

这样把一个有两个元素的订单流,变成了一个有六个元素的商品流

构建

根据已有的数组构建流

Arrays.stream(array)

根据已有的 Collection 构建流(包括 List,Set 等)

List.of("a","b","c").stream()

把一个对象变成流

Stream.of("d")

把多个对象变成流

Stream.of("x", "y")

拼接

两个流拼接

Stream.concat(Stream.of("a","b","c"), Stream.of("d"))

截取

Stream.concat(Stream.of("a", "b", "c"), Stream.of("d"))
    .skip(1)
    .limit(2)
  • skip 是跳过几个元素

  • limit 是限制处理的元素个数

  • dropWhile 是 drop 流中元素,直到条件不成立,留下剩余元素

  • takeWhile 是 take 流中元素,直到条件不成立,舍弃剩余元素

生成

生成从 0 ~ 9 的数字

IntStream.range(0, 10)

或者

IntStream.rangeClosed(0, 9)

如果想订制,可以用 iterate 方法,例如下面生成奇数序列

IntStream.iterate(1, x -> x + 2)
  • 参数1 是初始值
  • 参数2 是一个特殊 Function,即参数类型与返回值相同,它会根据上一个元素 x 的值计算出当前元素
  • 需要用 limit 限制元素个数

也可以用 iterate 的重载方法

IntStream.iterate(1, x -> x < 10, x -> x + 2)
  • 参数1 是初始值
  • 参数2 用来限制元素个数,一旦不满足此条件,流就结束
  • 参数3 相当于上个方法的参数2

iterate 的特点是根据上一个元素计算当前元素,如果不需要依赖上一个元素,可以改用 generate 方法

例如下面是生成 5 个随机 int

Stream.generate(()-> ThreadLocalRandom.current().nextInt()).limit(5)

不过如果只是生成随机数的话,有更简单的办法

ThreadLocalRandom.current().ints(5)

如果要指定上下限,例如下面是生成从 0~9 的100个随机数

ThreadLocalRandom.current().ints(100, 0, 10)

查找与判断

下面的代码找到流中任意(Any)一个偶数

int[] array = {1, 3, 5, 4, 7, 6, 9};

Arrays.stream(array)
    .filter(x -> (x & 1) == 0)
    .findAny()
    .ifPresent(System.out::println);
  • 注意 findAny 返回的是 OptionalInt 对象,因为可能流中不存在偶数
  • 对于 OptionalInt 对象,一般需要用 ifPresent 或 orElse(提供默认值)来处理

与 findAny 比较类似的是 firstFirst,它俩的区别

  • findAny 是找在流中任意位置的元素,不需要考虑顺序,对于上例返回 6 也是可以的
  • findFirst 是找第一个出现在元素,需要考虑顺序,对于上例只能返回 4
  • findAny 在顺序流中与 findFirst 表现相同,区别在于并行流下会更快

判断流中是否存在任意一个偶数

Arrays.stream(array).anyMatch(x -> (x & 1) == 0)
  • 它返回的是 boolean 值,可以直接用来判断

判断流是否全部是偶数

Arrays.stream(array).allMatch(x -> (x & 1) == 0)
  • 同样,它返回的是 boolean 值,可以直接用来判断

判断流是否全部不是偶数

Arrays.stream(array).noneMatch(x -> (x & 1) == 0)
  • noneMatch 与 allMatch 含义恰好相反

去重

distinct() 底层调用对象的equals方法

排序

已知有数据

record Hero(String name, int strength) { }

Stream.of(
    new Hero("独孤求败", 100),
    new Hero("令狐冲", 90),
    new Hero("风清扬", 98),
    new Hero("东方不败", 98),
    new Hero("方证", 92),
    new Hero("任我行", 92),
    new Hero("冲虚", 90),
    new Hero("向问天", 88),
    new Hero("不戒", 88)
)

要求,首先按 strength 武力排序(逆序),武力相同的,按姓名长度排序(正序)

仅用 lambda 来解

.sorted((a,b)-> {
    int res = Integer.compare(b.strength(), a.strength());
    return (res == 0) ? Integer.compare(a.nameLength(), b.nameLength()) : res; 
})

方法引用改写

.sorted(
    Comparator.comparingInt(Hero::strength)
      .reversed()
      .thenComparingInt(Hero::nameLength)
)

其中:

  • comparingInt 接收一个 key 提取器(说明按对象中哪部分来比较),返回一个比较器
  • reversed 返回一个顺序相反的比较器
  • thenComparingInt 接收一个 key 提取器,返回一个新比较器,新比较器在原有比较器结果相等时执行新的比较逻辑

增加一个辅助方法

record Hero(String name, int strength) {
    int nameLength() {
        return this.name.length();
    }
}

原理:

.sorted((e, f) -> {
    int res =
        ((Comparator<Hero>) (c, d) ->
            ((Comparator<Hero>) (a, b) -> Integer.compare(a.strength(), b.strength()))
                .compare(d, c))
            .compare(e, f);
    return (res == 0) ? Integer.compare(e.nameLength(), f.nameLength()) : res;
})

如果不好看,改成下面的代码

.sorted(step3(step2(step1())))

static Comparator<Hero> step1() {
    return (a, b) -> Integer.compare(a.strength(), b.strength());
}

static Comparator<Hero> step2(Comparator<Hero> step1) {
    return (c, d) -> step1.compare(d, c);
}

static Comparator<Hero> step3(Comparator<Hero> step2) {
    return (e, f) -> {
        int res = step2.compare(e, f);
        return (res == 0) ? Integer.compare(e.nameLength(), f.nameLength()) : res;
    };
}

化简

reduce(init, (p,x) -> r)

  • init 代表初始值
  • (p,x) -> r 是一个 BinaryOperator,作用是根据上次化简结果 p 和当前元素 x,得到本次化简结果 r

这样两两化简,可以将流中的所有元素合并成一个结果

收集

collect( supplier, accumulator, combiner)

  • supplier 是描述如何创建收集容器 c :()-> c
  • accumulator 是描述如何向容器 c 添加元素 x:(c, x) -> void
  • combiner 是描述如何合并两个容器:(c1, c2) -> void
    • 串行流下不需要合并容器
    • 并行流如果用的是并发容器,也不需要合并

收集器

Collectors 类中提供了很多现成的收集器,详情见网页

下游收集器

做 groupingBy 分组收集时,组内可能需要进一步的数据收集,称为下游收集器,详情见网页

基本流

基本类型流指 IntStream、LongStream 和 DoubleStream,它们在做数值计算时有更好的性能。

转换成基本流

  • mapToInt
  • mapToLong
  • mapToDouble
  • flatMapToInt
  • flatMapToLong
  • flatMapToDouble
  • mapMultiToInt
  • mapMultiToLong
  • mapMultiToDouble

基本流转对象流

  • mapToObj
  • boxed

特性

  1. 一次使用:流只能使用一次(终结方法只能调用一次)
  2. 两类操作:
    1. 中间操作,lazy 懒惰的
    2. 终结操作,eager 迫切的

并行

Stream.of(1, 2, 3, 4)
    .parallel()
    .collect(Collector.of(
            () -> {
                System.out.printf("%-12s %s%n",simple(),"create");
                return new ArrayList<Integer>();
            },
            (list, x) -> {
                List<Integer> old = new ArrayList<>(list);
                list.add(x);
                System.out.printf("%-12s %s.add(%d)=>%s%n",simple(), old, x, list);
            },
            (list1, list2) -> {
                List<Integer> old = new ArrayList<>(list1);
                list1.addAll(list2);
                System.out.printf("%-12s %s.add(%s)=>%s%n", simple(),old, list2, list1);
                return list1;
            },
            list -> list,
            Collector.Characteristics.IDENTITY_FINISH
    ));

四. 实现原理

lambda 原理

以下面代码为例

public class TestLambda {
    public static void main(String[] args) {
        test((a, b) -> a + b);
    }

    static void test(BinaryOperator<Integer> lambda) {
        System.out.println(lambda.apply(1, 2));
    }
}

执行结果

3

第一步,生成静态方法

如何证明?用反射

for (Method method : TestLambda.class.getDeclaredMethods()) {
    System.out.println(method);
}

输出为(去掉了包名,容易阅读)

public static void TestLambda.main(java.lang.String[])
static void TestLambda.test(BinaryOperator)
private static java.lang.Integer TestLambda.lambda$main$0(Integer,Integer)
  • 可以看到除了我们自己写的 main 和 test 以外,多出一个名为 lambda$main$0 的方法

  • 这个方法是在编译期间由编译器生成的方法,是 synthetic(合成)方法

  • 它的参数、内容就是 lambda 表达式提供的参数和内容,如下面代码片段所示

    private static Integer lambda$main$0(Integer a, Integer b) {
        return a + b;
    }
    

第二步,生成实现类字节码

如果是我自己造一个对象包含此方法,可以这么做

先创建一个类

final class LambdaObject implements BinaryOperator<Integer> {

    @Override
    public Integer apply(Integer a, Integer b) {
        return TestLambda.lambda$main$0(a, b);
    }
}

将来使用时,创建对象

test(new LambdaObject());

只不过,jvm 是在运行期间造出的这个类以及对象而已,要想查看这个类

在 jdk 21 中运行时添加虚拟机参数

-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles

早期 jdk 添加的参数是(没有去进行版本比对了)

-Djdk.internal.lambda.dumpProxyClasses

若想实现在运行期间生成上述 class 字节码,有两种手段

  • 一是动态代理,jdk 并没有采用这种办法来生成 Lambda 类
  • 二是用 LambdaMetaFactory,它配合 MethodHandle API 在执行时更具性能优势
public class TestLambda1 {
    public static void main(String[] args) throws Throwable {
        test((a, b) -> a + b);

        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodType factoryType = MethodType.methodType(BinaryOperator.class);
        MethodType interfaceMethodType = MethodType.methodType(Object.class, Object.class, Object.class);
        MethodType implementsMethodType = MethodType.methodType(Integer.class, Integer.class, Integer.class);

        MethodHandle implementsMethod = lookup.findStatic(TestLambda1.class, "lambda$main$1", implementsMethodType);

        MethodType lambdaType = MethodType.methodType(Integer.class, Integer.class, Integer.class);
        CallSite callSite = LambdaMetafactory.metafactory(lookup,
                "apply", factoryType, interfaceMethodType,
                implementsMethod,
                lambdaType);

        BinaryOperator<Integer> lambda = (BinaryOperator) callSite.getTarget().invoke();
        test(lambda);
    }

    static Integer lambda$main$1(Integer a, Integer b) {
        return a + b;
    }

    static void test(BinaryOperator<Integer> lambda) {
        System.out.println(lambda.apply(1, 2));
    }
}

其中

  • “apply” 是接口方法名

  • factoryType 是工厂方法长相

  • interfaceMethodType 是接口方法长相

  • implementsMethod 是实现方法

    • implementsMethodType 是实现方法长相
  • lambdaType 是实际函数对象长相

  • callSite.getTarget() 实际是调用实现类的构造方法对应的 mh,最后 invoke 返回函数对象

方法引用原理

public class TestLambda3 {
    public static void main(String[] args) throws Throwable {
		test(String::toLowerCase);
        
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodType factoryType = MethodType.methodType(Function.class);
        MethodType interfaceMethodType = MethodType.methodType(Object.class, Object.class);
        MethodHandle implementsMethod = lookup.findVirtual(String.class, "toLowerCase", MethodType.methodType(String.class));
        MethodType lambdaType = MethodType.methodType(String.class, String.class);
        CallSite callSite = LambdaMetafactory.metafactory(lookup,
                "apply", factoryType, interfaceMethodType,
                implementsMethod,
                lambdaType);

        Function<String, String> lambda = (Function<String, String>) callSite.getTarget().invoke();
        System.out.println(lambda.apply("Tom"));
    }

    static void test(Function<String,String> lambda) {
        System.out.println(lambda.apply("Tom"));
    }
}

闭包原理

捕获基本类型变量

int c = 10;
test((a, b) -> a + b + c);

static void test(BinaryOperator<Integer> lambda) {
    System.out.println(lambda.apply(1, 2));
}

生成一个带 3 个参数的方法,但它和 BinaryOperator 还差一个 int 参数

static Integer lambda$main$1(int c, Integer a, Integer b) {
    return a + b + c;
}
public class TestLambda2 {
    public static void main(String[] args) throws Throwable {
//        int c = 10;
//        test((a, b) -> a + b + c);

        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodType factoryType = MethodType.methodType(BinaryOperator.class, int.class);
        MethodType interfaceMethodType = MethodType.methodType(Object.class, Object.class, Object.class);
        MethodType implementsMethodType = MethodType.methodType(Integer.class, int.class, Integer.class, Integer.class);

        MethodHandle implementsMethod = lookup.findStatic(TestLambda2.class, "lambda$main$1", implementsMethodType);

        MethodType lambdaType = MethodType.methodType(Integer.class, Integer.class, Integer.class);
        CallSite callSite = LambdaMetafactory.metafactory(lookup,
                "apply", factoryType, interfaceMethodType,
                implementsMethod,
                lambdaType);

        BinaryOperator<Integer> lambda = (BinaryOperator) callSite.getTarget().invoke(10);
        test(lambda);
    }

    static Integer lambda$main$1(int c, Integer a, Integer b) {
        return a + b + c;
    }

    static void test(BinaryOperator<Integer> lambda) {
        System.out.println(lambda.apply(1, 2));
    }
}

不同之处

  • factoryType,除了原本的接口类型之外,多了实现方法第一个参数的类型
  • 产生 lambda 对象的时候,通过 invoke 把这个参数的实际值传进去

这样产生的 LambdaType 就是这样,并且生成 Lambda 对象时,c 的值被固定为 10

final class LambdaType implements BinaryOperator {
    private final int c;

    private TestLambda2$$Lambda(int c) {
        this.c = c;
    }

    public Object apply(Object a, Object b) {
        return TestLambda2.lambda$main$1(this.c, (Integer)a, (Integer)b);
    }
}

捕获引用类型变量

public class TestLambda4 {
    static class MyRef {
        int age;

        public MyRef(int age) {
            this.age = age;
        }
    }
    public static void main(String[] args) throws Throwable {
        /*MyRef ref = new MyRef(10);
        test((a, b) -> a + b + ref.age);*/

        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodType factoryType = MethodType.methodType(BinaryOperator.class, MyRef.class);
        MethodType interfaceMethodType = MethodType.methodType(Object.class, Object.class, Object.class);
        MethodType implementsMethodType = MethodType.methodType(Integer.class, MyRef.class, Integer.class, Integer.class);

        MethodHandle implementsMethod = lookup.findStatic(TestLambda4.class, "lambda$main$1", implementsMethodType);

        MethodType lambdaType = MethodType.methodType(Integer.class, Integer.class, Integer.class);
        CallSite callSite = LambdaMetafactory.metafactory(lookup,
                "apply", factoryType, interfaceMethodType,
                implementsMethod,
                lambdaType);

        BinaryOperator<Integer> lambda = (BinaryOperator) callSite.getTarget().bindTo(new MyRef(20)).invoke();
        test(lambda);
    }

    static Integer lambda$main$1(MyRef c, Integer a, Integer b) {
        return a + b + c.age;
    }

    static void test(BinaryOperator<Integer> lambda) {
        System.out.println(lambda.apply(1, 2));
    }
}

与捕获基本类型变量类似,不过

除了

callSite.getTarget().invoke(new MyRef(20));

还可以

callSite.getTarget().bindTo(new MyRef(20)).invoke();

Stream 构建

自定义可切分迭代器

public class TestSpliterator {
    static class MySpliterator<T> implements Spliterator<T> {
        T[] array;
        int begin;
        int end;

        public MySpliterator(T[] array, int begin, int end) {
            this.array = array;
            this.begin = begin;
            this.end = end;
        }

        @Override
        public boolean tryAdvance(Consumer<? super T> action) {
            if (begin > end) {
                return false;
            }
            action.accept(array[begin++]);
            return true;
        }

        @Override
        public Spliterator<T> trySplit() {
            if (estimateSize() > 5) {
                int mid = (begin + end) >>> 1;
                MySpliterator<T> res = new MySpliterator<>(array, begin, mid);
                System.out.println(Thread.currentThread().getName() + "=>" + res);
                begin = mid + 1;
                return res;
            }
            return null;
        }

        @Override
        public String toString() {
            return Arrays.toString(Arrays.copyOfRange(array, begin, end + 1));
        }

        @Override
        public long estimateSize() {
            return end - begin + 1;
        }

        @Override
        public int characteristics() {
            return Spliterator.SUBSIZED | Spliterator.ORDERED;
        }
    }

    public static void main(String[] args) {
        Integer[] all = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        MySpliterator<Integer> spliterator = new MySpliterator<>(all, 0, 9);


        StreamSupport.stream(spliterator, false)
                .parallel()
                .forEach(x -> System.out.println(Thread.currentThread().getName() + ":" + x));
    }
}