当前位置: 首页 > news >正文

Java 基础——函数式编程

目录

  • 1.函数
    • 1.1.什么是函数?
    • 1.2.函数不变性
      • 1.2.1.什么是函数不变性?
      • 1.2.2.函数与方法
      • 1.2.3.不变的好处
    • 1.3.函数对象
      • 1.3.1.函数化对象
      • 1.3.2.行为参数化
      • 1.3.3.延迟执行
      • 函数对象的不同类型
  • 2.函数编程语法
    • 2.1.表现形式
    • 2.2.函数类型
    • 2.3.六种方法引用
      • 2.3.1.类名::静态方法名
      • 2.3.2.类名::非静态方法名
      • 2.3.3.对象::非静态方法名
      • 2.3.4.类名::new
      • 2.3.5.this::非静态方法名
      • 2.3.6.super::非静态方法名
      • 2.3.7.特例
    • 2.4.闭包 (Closure)
    • 2.5.柯里化 (Carrying)
    • 2.6.综合练习
      • 2.6.1.判断语法正确性
      • 2.6.2.写出等价的 lambda 表达式
  • 3.Stream API
    • 3.1.过滤
    • 3.2.映射
    • 3.3.降维
    • 3.4.构建
    • 3.5.拼接
    • 3.6.截取
    • 3.7.生成
    • 3.8.查找与判断
    • 3.9.排序与去重
    • 3.10.化简
    • 3.11.收集
    • 3.12.⭐收集器
      • 3.12.1.常见收集器
      • 3.12.2.下游收集器
    • 3.13.基本流
    • 3.14.⭐特性
    • 3.15.并行
  • 4. 实现原理
    • 4.1.lambda 原理
      • 4.1.2.第一步:生成静态方法
      • 4.1.3.第二步:生成实现类字节码
    • 4.2.方法引用原理
    • 4.3.闭包原理
    • 4.4.Stream 构建

本文笔记整理自黑马程序员视频 https://www.bilibili.com/video/BV1fz421C7tj/,相关资料可在该视频评论区领取。

1.函数

1.1.什么是函数?

什么是函数呢?函数即规则数学上:
在这里插入图片描述
例如:

INPUTf(x)OUTPUT
1?1
2?4
3?9
4?16
5?25
  • f(x)=x2f(x) = x^2f(x)=x2 是一种规律, input 按照此规律变化为 output
  • 很多规律已经由人揭示,例如 e=m⋅c2e = m \cdot c^2e=mc2
  • 程序设计中更可以自己去制定规律,一旦成为规则的制定者,你就是神

1.2.函数不变性

1.2.1.什么是函数不变性?

只要输入相同,无论多少次调用,无论什么时间调用,输出相同。例如:

public class TestMutable {public static void main(String[] args) {System.out.println(pray("张三"));System.out.println(pray("张三"));System.out.println(pray("张三"));}static class Buddha {String name;public Buddha(String name) {this.name = name;}}static Buddha buddha = new Buddha("佛祖");static String pray(String person) {return (person + "向[" + buddha.name + "]虔诚祈祷");}
}

以上 pray 的执行结果,除了参数变化外,希望函数的执行规则永远不变

张三向[佛祖]虔诚祈祷
张三向[佛祖]虔诚祈祷
张三向[佛祖]虔诚祈祷

然而,由于设计上的缺陷,函数引用了外界可变的数据,如果这么使用

buddha.name = "魔王";
System.out.println(pray("张三"));

结果就会是

张三向[魔王]虔诚祈祷

问题出在哪儿呢?函数的目的是除了参数能变化,其它部分都要不变,这样才能成为规则的一部分。佛祖要成为规则的一部分,也要保持不变。改正方法如下:

static class Buddha {final String name;public Buddha(String name) {this.name = name;}
}

record Buddha(String name) { }

注意:不是说函数不能引用外界的数据,而是它引用的数据必须也能作为规则的一部分。

1.2.2.函数与方法

方法本质上也是函数,不过方法绑定在对象之上,它是对象的个人法则。

1.2.3.不变的好处

只有不变,才能在滚滚时间洪流中屹立不倒,成为规则的一部分,例如在多线程编程中,不变意味着线程安全。

1.3.函数对象

1.3.1.函数化对象

函数本无形,也就是它代表的规则:位置固定、不能传播。若要有形,让函数的规则能够传播,需要将函数化为对象

public class MyClass {static int add(int a, int b) {return a + b;}
} 

interface Lambda {int calculate(int a, int b);
}Lambda add = (a, b) -> a + b; // 它已经变成了一个 lambda 对象

区别在哪?

  • 前者是纯粹的一条两数加法规则,它的位置是固定的,要使用它,需要通过 MyClass.add 找到它,然后执行;
  • 而后者(add 对象)就像长了腿,它的位置是可以变化的,想去哪里就去哪里,哪里要用到这条加法规则,把它传递过去;
  • 接口的目的是为了将来用它来执行函数对象,此接口中只能有一个方法定义;

函数化为对象做个比喻:

  • 之前是大家要统一去西天取经;
  • 现在是每个菩萨、罗汉拿着经书,入世传经;

例如:

public class Test {interface Lambda {int calculate(int a, int b);}static class Server {public static void main(String[] args) throws IOException {ServerSocket ss = new ServerSocket(8080);System.out.println("server start...");while (true) {Socket s = ss.accept();Thread.ofVirtual().start(() -> {try {ObjectInputStream is = new ObjectInputStream(s.getInputStream());Lambda lambda = (Lambda) is.readObject();int a = ThreadLocalRandom.current().nextInt(10);int b = ThreadLocalRandom.current().nextInt(10);System.out.printf("%s %d op %d = %d%n",s.getRemoteSocketAddress().toString(), a, b, lambda.calculate(a, b));} catch (IOException | ClassNotFoundException e) {throw new RuntimeException(e);}});}}}static class Client1 {public static void main(String[] args) throws IOException {try(Socket s = new Socket("127.0.0.1", 8080)){Lambda lambda = (Lambda & Serializable) (a, b) -> a + b;ObjectOutputStream os = new ObjectOutputStream(s.getOutputStream());os.writeObject(lambda);os.flush();}}}static class Client2 {public static void main(String[] args) throws IOException {try(Socket s = new Socket("127.0.0.1", 8080)){Lambda lambda = (Lambda & Serializable) (a, b) -> a - b;ObjectOutputStream os = new ObjectOutputStream(s.getOutputStream());os.writeObject(lambda);os.flush();}}}static class Client3 {public static void main(String[] args) throws IOException {try(Socket s = new Socket("127.0.0.1", 8080)){Lambda lambda = (Lambda & Serializable) (a, b) -> a * b;ObjectOutputStream os = new ObjectOutputStream(s.getOutputStream());os.writeObject(lambda);os.flush();}}}
}

上面的例子做了一些简单的扩展,可以看到不同的客户端可以上传自己的计算规则

P.S.

  • 大部分文献都说 lambda 是匿名函数,但我觉得需要在这个说法上进行补充;
  • 至少在 Java 里,虽然 lambda 表达式本身不需要起名字,但得提供一个对应接口;

1.3.2.行为参数化

已知学生类定义如下:

static class Student {private String name;private int age;private String sex;public Student(String name, int age, String sex) {this.name = name;this.age = age;this.sex = sex;}public int getAge() {return age;}public String getName() {return name;}public String getSex() {return sex;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", sex='" + sex + '\'' +'}';}
}

针对一组学生集合,筛选出男学生,下面的代码实现如何,评价一下:

public static void main(String[] args) {List<Student> students = List.of(new Student("张无忌", 18, "男"),new Student("杨不悔", 16, "女"),new Student("周芷若", 19, "女"),new Student("宋青书", 20, "男"));System.out.println(filter(students)); // 能得到 张无忌,宋青书
}static List<Student> filter(List<Student> students) {List<Student> result = new ArrayList<>();for (Student student : students) {if (student.sex.equals("男")) {result.add(student);}}return result;
}

如果需求再变动一下,要求找到 18 岁以下的学生,上面代码显然不能用了,改动方法如下:

static List<Student> filter(List<Student> students) {List<Student> result = new ArrayList<>();for (Student student : students) {if (student.age <= 18) {result.add(student);}}return result;
}System.out.println(filter(students)); // 能得到 张无忌,杨不悔

那么需求如果再要变动,找18岁以下男学生,怎么改?显然上述做法并不太好。更希望一个方法能处理各种情况,仔细观察以上两个方法,找不同。不同在于筛选条件部分:

student.sex.equals("男")

student.age <= 18

既然它们就是不同,那么能否把它作为参数传递进来,这样处理起来不就一致了吗?

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

它俩要判断的逻辑不同,那这两处不同的逻辑必然要用函数来表示,将来这两个函数都需要用到 student 对象来判断,都应该返回一个 boolean 结果,怎么描述函数的长相呢?

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);

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

1.3.3.延迟执行

在记录日志时,假设日志级别是 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 条件成立时才执行。

函数对象的不同类型

Comparator<Student> c = (Student s1, Student s2) -> Integer.compare(s1.age, s2.age);BiFunction<Student, Student, Integer> f =  (Student s1, Student s2) -> Integer.compare(s1.age, s2.age);

2.函数编程语法

2.1.表现形式

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

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

在这里插入图片描述

2.2.函数类型

(1)函数式接口的命名规律:

  • 带有 Unary 是一元的意思,表示一个参数;
  • 带有 Bi 或 Binary 是二元的意思,表示两个参数;
  • Ternary 表示三元;
  • Quatenary 表示四元;

在这里插入图片描述

(2)函数式接口的常见名称及含义如下:

在这里插入图片描述

2.3.六种方法引用

方法引用也是类似,入参类型、返回值类型都一致的话,可以看作同一类的对象,也是用函数式接口表示。

2.3.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 对象即可;
  • 在方法引用 Type2Test::isMale 中,isMale 这个静态方法有入参 Student 对应 T,有返回值 boolean 也能对应上,所以可以直接使用;
public interface Stream<T> extends BaseStream<T, Stream<T>> {//...Stream<T> filter(Predicate<? super T> predicate);
}

上述代码的输出如下:

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

2.3.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;}}
}

上例中函数式接口 Type3 中 transfer

  • 参数 1 stu 对应 hello 方法所属 Student 类的对象;
  • 参数 2 message 对应 hello 方法自己的参数 message
  • 返回值对应着 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 类型的返回值;
  • 它的第一个入参 T 对应着非静态方法 name() 的所属类 Student 的对象;
  • 它没有剩余参数, name() 方法也没有参数;
  • 它的返回值 R 对应着 name() 方法的返回值 String;
public interface Stream<T> extends BaseStream<T, Stream<T>> {//...<R> Stream<R> map(Function<? super T, ? extends R> mapper);}

输出如下:

张无忌
周芷若
宋青书

注:record 类的字段会自动生成访问方法,这些方法的名字与字段名相同。例如,Student 类中的 name 字段会有一个名为 name() 的方法。

2.3.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 类型的参数,无返回值;
  • System.out 对象中有非静态方法 void println(Object x) 与之一致,因此可以将此方法化为 lambda 对象给 forEach 使用;
public interface Stream<T> extends BaseStream<T, Stream<T>> {//...void forEach(Consumer<? super T> action);
}
public class PrintStream extends FilterOutputStream implements Appendable, Closeable//...public void println(Object x) {String s = String.valueOf(x);if (getClass() == PrintStream.class) {// need to apply String.valueOf again since first invocation// might return nullwriteln(String.valueOf(s));} else {synchronized (this) {print(s);newLine();}}}
}

2.3.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;}@Overridepublic 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));}
}

输出结果如下:

Student{name='某人', age=18}
Student{name='张三', age=18}
Student{name='李四', age=20}

2.3.5.this::非静态方法名

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);}}
}

输出结果如下:

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

2.3.6.super::非静态方法名

super::非静态方法名是第 2 种方法引用的特例,只能用在类内部(用在要用 super 区分重载方法时)。

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

2.3.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 接口不需要返回值,而实际的函数对象多出的返回值也不影响使用。

2.4.闭包 (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+yy \rightarrow x + yyx+yx=10x = 10x=10,就形成了一个闭包;
  • 可以想象成,函数对象有个背包,背包里可以装变量随身携带,将来函数对象甭管传递到多远的地方,包里总装着个 x=10x = 10x=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.newFixedThreadPool(10);for (Runnable task : list) {service.submit(task);}System.in.read();}
}

2.5.柯里化 (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) 分析过程类似;

2.6.综合练习

2.6.1.判断语法正确性

interface Lambda1 {int op(int a, int b);
}interface Lambda2 {void op(Object obj);
}
  1. Lambda1 lambda = a, b -> a - b
  2. Lambda1 lambda = (c, d) -> c * d
  3. Lambda1 lambda = (int a, b) -> a + b
  4. Lambda2 lambda = Object a -> System.out.println(a)

2.6.2.写出等价的 lambda 表达式

static class Student {private String name;public Student(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return Objects.equals(name, student.name);}@Overridepublic int hashCode() {return Objects.hash(name);}
}
  1. Math::random

    ()->Math.random()

  2. Math::sqrt

    (double number)->Math.sqrt(number)

  3. Student::getName

    (Student stu)->stu.getName()

  4. Student::setName

    (Student stu, String newName) -> stu.setName(newName)

  5. Student::hashCode

    (Student stu) -> stu.hashCode()

  6. Student::equals

    (Student stu, Object o) -> stu.equals(o)

假设已有对象 Student stu = new Student("张三");

  1. stu::getName

    ()->stu.getName()

  2. stu::setName

    (String newName)->stu.setName(newName)

  3. Student::new

    (String name)->new Student(name)

3.Stream API

3.1.过滤

public interface Stream<T> extends BaseStream<T, Stream<T>> {//...Stream<T> filter(Predicate<? super T> predicate);
}
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("蓝色")))

3.2.映射

public interface Stream<T> extends BaseStream<T, Stream<T>> {//...<R> Stream<R> map(Function<? super T, ? extends R> mapper);
}

在这里插入图片描述

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

3.3.降维

public interface Stream<T> extends BaseStream<T, Stream<T>> {//...<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
}

例1

在这里插入图片描述

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())

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

3.4.构建

public interface Stream<T> extends BaseStream<T, Stream<T>> {//...public static<T> Stream<T> of(T... values) {return Arrays.stream(values);}
}

根据已有的数组构建流

Arrays.stream(array)

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

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

把一个对象变成流

Stream.of("d")

把多个对象变成流

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

3.5.拼接

public interface Stream<T> extends BaseStream<T, Stream<T>> {//...public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {Objects.requireNonNull(a);Objects.requireNonNull(b);@SuppressWarnings("unchecked")Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>((Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());return stream.onClose(Streams.composedClose(a, b));}
}

两个流拼接

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

3.6.截取

Stream.concat(Stream.of("a", "b", "c"), Stream.of("d")).skip(1).limit(2)
  • skip 是跳过几个元素
  • limit 是限制处理的元素个数
  • dropWhile 是 drop 流中元素,直到条件不成立,留下剩余元素
  • takeWhile 是 take 流中元素,直到条件不成立,舍弃剩余元素

3.7.生成

生成从 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)

3.8.查找与判断

下面的代码找到流中任意(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 含义恰好相反

3.9.排序与去重

已知有数据

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;};
}

3.10.化简

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

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

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

3.11.收集

public interface Stream<T> extends BaseStream<T, Stream<T>> {//...<R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner);<R, A> R collect(Collector<? super T, A, R> collector);
}

其中,collect(supplier, accumulator, combiner) 中的:

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

3.12.⭐收集器

3.12.1.常见收集器

Collectors 类中提供了很多现成的收集器:

(1)收集到 List

public static void main(String[] args) {Stream<String> stream = Stream.of("令狐冲", "风清扬", "独孤求败", "方证","东方不败", "冲虚", "向问天", "任我行", "不戒");/*List<String> result = stream.collect(() -> new ArrayList<>(), (list, x) -> list.add(x), (a, b) -> { });ArrayList::new   () -> new ArrayList()ArrayList::add   (list,x) -> list.add(x)List<String> result = stream.collect(ArrayList::new, ArrayList::add, (a, b) -> { });*/List<String> result = stream.collect(Collectors.toList());
}

(2)收集到 Set

//Set<String> result = stream.collect(LinkedHashSet::new, Set::add, (a, b) -> { });
Set<String> result = stream.collect(Collectors.toSet());

(3)收集到 StringBuilder

//StringBuilder sb = stream.collect(StringBuilder::new, StringBuilder::append, (a,b) -> {});
String result = stream.collect(Collectors.joining());

(4)收集到 StringJoiner

//StringJoiner sb = stream.collect(()-> new StringJoiner(","), StringJoiner::add, (a,b)->{});
String result = stream.collect(Collectors.joining(","));

(5)收集到 Map

//Map<String, Integer> result = stream.collect(HashMap::new, (map, x) -> map.put(x, 1), (a, b) -> {});
Map<String, Integer> map = stream.collect(Collectors.toMap(x -> x, x -> 1));

3.12.2.下游收集器

(1)做 groupingBy 分组收集时,组内可能需要进一步的数据收集,称为下游收集器。上述收集器均可以作为下游收集器。

Stream<String> stream = Stream.of("令狐冲", "风清扬", "独孤求败", "方证","东方不败", "冲虚", "向问天", "任我行", "不戒");
Map<Integer, String> result = stream.collect(Collectors.groupingBy(x -> x.length(), Collectors.joining(",")));
for (Map.Entry<Integer, String> e : result.entrySet()) {System.out.println(e);
}

输出结果如下:

2=方证,冲虚,不戒
3=令狐冲,风清扬,向问天,任我行
4=独孤求败,东方不败

(2)其它下游收集器

record Hero(String name, int strength) {}Stream<Hero> stream = Stream.of(new Hero("令狐冲", 90),new Hero("风清扬", 98),new Hero("独孤求败", 100),new Hero("方证", 92),new Hero("东方不败", 98),new Hero("冲虚", 90),new Hero("向问天", 88),new Hero("任我行", 92),new Hero("不戒", 88)
);
/*1.mapping(x->y, dc)  需求:根据名字长度分组,分组后组内只保留他们的武力值new Hero("令狐冲", 90) -> 90dc 下游收集器(down collector)
*/
Map<Integer, List<Integer>> collect = stream.collect(Collectors.groupingBy(h -> h.name().length(), mapping(h -> h.strength(), toList())));
for (Map.Entry<Integer, List<Integer>> e : collect.entrySet()) {System.out.println(e);
}

输出结果如下:

2=[92, 90, 88]
3=[90, 98, 88, 92]
4=[100, 98]
//2.filtering(x -> boolean, dc)  需求:根据名字长度分组,分组后组内过滤掉武力小于 90 的
//在分组收集的过程中,执行过滤
Map<Integer, List<Hero>> collect1 = stream.collect(groupingBy(h -> h.name().length(), filtering(h -> h.strength() >= 90, toList())));
for (Map.Entry<Integer, List<Hero>> e : collect1.entrySet()) {System.out.println(e);
}
//先过滤,再来分组收集
Map<Integer, List<Hero>> collect2 = stream.filter(h -> h.strength() >= 90).collect(groupingBy(h -> h.name().length(), toList()));

输出结果如下:

2=[Hero[name=方证, strength=92], Hero[name=冲虚, strength=90]]
3=[Hero[name=令狐冲, strength=90], Hero[name=风清扬, strength=98], Hero[name=任我行, strength=92]]
4=[Hero[name=独孤求败, strength=100], Hero[name=东方不败, strength=98]]
//3.flatMapping(x->substream, dc)     
//需求:根据名字长度分组,分组后组内保留人名,并且人名切分成单个字符
Map<Integer, List<String>> collect = stream.collect(groupingBy(h -> h.name().length(),flatMapping(h -> h.name().chars().mapToObj(Character::toString), toList())));
for (Map.Entry<Integer, List<String>> e : collect.entrySet()) {System.out.println(e);
}

输出结果如下:

2=[,,,,,]
3=[,,,,,,,,,,,]
4=[,,,,,,,]
//4.counting() 需求:根据名字长度分组,分组后求每组个数
Map<Integer, Long> collect = stream.collect(groupingBy(h -> h.name().length(), counting()));
for (Map.Entry<Integer, Long> e : collect.entrySet()) {System.out.println(e);
}

输出结果如下:

2=3
3=4
4=2
//5. minBy((a,b)->int) 需求:根据名字长度分组,分组后求每组武功最低的人
//6. maxBy((a,b)->int) 需求:根据名字长度分组,分组后求每组武功最高的人
Map<Integer, Optional<Hero>> collect = stream.collect(groupingBy(h -> h.name().length(), maxBy(Comparator.comparingInt(Hero::strength))));
for (Map.Entry<Integer, Optional<Hero>> e : collect.entrySet()) {System.out.println(e);
}

输出结果如下:

2=Optional[Hero[name=方证, strength=92]]
3=Optional[Hero[name=风清扬, strength=98]]
4=Optional[Hero[name=独孤求败, strength=100]]
/*
7. summingInt(x->int)            需求:根据名字长度分组,分组后求每组武力和
8. averagingDouble(x->double)    需求:根据名字长度分组,分组后求每组武力平均值
*/
Map<Integer, Double> collect = stream.collect(groupingBy(h -> h.name().length(), averagingDouble(h -> h.strength())));
/*
9. reducing(init,(p,x)->r)求和
stream.collect(groupingBy(h -> h.name().length(), mapping(h -> h.strength(), reducing(0, (p, x) -> p + x))));
求个数
stream.collect(groupingBy(h -> h.name().length(), mapping(h -> 1, reducing(0, (p, x) -> p + x))));
*/

3.13.基本流

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

在这里插入图片描述

(2)转换成基本流:

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

基本流转对象流:

  • mapToObj
  • boxed

3.14.⭐特性

  • 一次使用:流只能使用一次(终结方法只能调用一次)
  • 两类操作:
    • 中间操作:lazy 懒惰的,例如 filter、map;
    • 终结操作:eager 迫切的,例如 forEach;
  • Stream 常见的方法如下所示:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3.15.并行

想提高对流的操作效率,我们可以使用 parallel() 方法来使用并行流。此外,为了直观地验证是否并行操作流了,我们可以通过 Collector.of 方法来自定义收集器,从而打印相应的日志来便于观察。

public class ParallelExample {public static void main(String[] args) {Stream.of(1, 2, 3, 4).parallel().collect(Collector.of(() -> {                    // 1.如何创建容器System.out.printf("%-12s %s%n", simple(), "create");return new ArrayList<Integer>();},(list, x) -> {            // 2.如何向容器添加数据List<Integer> old = new ArrayList<>(list);list.add(x);System.out.printf("%-12s %s.add(%d)=>%s%n", simple(), old, x, list);},(list1, list2) -> {        // 3.如何合并两个容器的数据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 -> {       // 4.收尾System.out.printf("%-12s finish %s=>%s%n", simple(), list, list);return list;},Collector.Characteristics.IDENTITY_FINISH,                   // 不需要收尾Collector.Characteristics.UNORDERED,                         // 不需要保证顺序Collector.Characteristics.CONCURRENT                         // 容器需要支持并发));}private static String simple() {String name = Thread.currentThread().getName();int idx = name.indexOf("worker");if (idx > 0) {return name.substring(idx);}return name;}
}

打印结果如下:

main         create
main         [].add(3)=>[3, 2, 1, 4]
worker-1     [3].add(2)=>[3, 2, 1, 4]
worker-2     [3, 2].add(1)=>[3, 2, 1, 4]
worker-3     [3, 2, 1].add(4)=>[3, 2, 1, 4]

注意:

  • 数据量问题:数据量大时才建议用并行流;
  • 线程会无限增加吗:跟 CPU 能处理的线程数相关;
  • 收尾的意义:转不可变集合、StringBuilder 转 String …
  • 是否线程安全:不会有线程安全问题
  • 特性:
    • 是否需要收尾(默认收尾);
    • 是否需要保证顺序(默认保证);
    • 容器是否支持并发(默认不需要支持);
  • 到达选择哪一种?
    • A. Characteristics.CONCURRENT + Characteristics.UNORDERED + 线程安全容器:并发量大性能可能会受影响;
    • B. 默认 + 线程不安全容器:占用内存多,合并多也会影响性能;

4. 实现原理

4.1.lambda 原理

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

  • 方法从哪来 : 编译器发现代码中出现了 lambda,就会在当前类中生成 private static 方法,方法内包含的就是 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

4.1.2.第一步:生成静态方法

如何证明?用反射

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;
}

4.1.3.第二步:生成实现类字节码

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

final class LambdaObject implements BinaryOperator<Integer> {@Overridepublic 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 返回函数对象

4.2.方法引用原理

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"));}
}

4.3.闭包原理

捕获基本类型变量

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();

4.4.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;}@Overridepublic boolean tryAdvance(Consumer<? super T> action) {if (begin > end) {return false;}action.accept(array[begin++]);return true;}@Overridepublic 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;}@Overridepublic String toString() {return Arrays.toString(Arrays.copyOfRange(array, begin, end + 1));}@Overridepublic long estimateSize() {return end - begin + 1;}@Overridepublic 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));}
}

练习:按每次切分固定大小来实现

http://www.dtcms.com/a/557489.html

相关文章:

  • webkitx(Android WebView 最佳实践库)
  • 调查网站做调查不容易过横栏建设网站
  • 勐海县住房和城乡建设局网站南昌做网站费用
  • 感知上下文并可解释地预测合成致死药物靶点的大语言模型研究
  • AI研究-117 特斯拉 FSD 视觉解析:多摄像头 - 3D占用网络 - 车机渲染,盲区与低速复杂路况安全指南
  • 二级域名可以做网站吗免费个人博客网站模板下载
  • 复原大唐3d项目测试版
  • 2024年MySQL 下载、安装及启动停止教程(非常
  • 兰州百度网站建设百度网站关键词优化在哪里做
  • Redis——Windows安装
  • 微信网站开发视频教程免费的黄金软件
  • 【高级机器学习】0. Machine Learning 介绍
  • 昆明城乡和住房建设局网站网站做5级分销合法吗
  • .NETCore、.NET 7 和 RabbitMQ 的发布-订阅模式
  • Crashpad 在windows下编译和使用指南
  • 基于SpringBoot+Vue的农产品销售系统【协同过滤推荐算法+可视化统计】
  • 基于flet的一款windows桌面应用,实现了浏览图片、音乐、小说、各种资源的功能
  • 【开题答辩过程】以《基于微信小程序的线上讲座管理系统》为例,不会开题答辩的可以进来看看
  • 怎么做好网站建设新手怎么开传媒公司
  • 2025年8月AGI月评|AI开源项目全解析:从智能体到3D世界,技术边界再突破
  • CSP-J/S 2025 游记
  • 深入洞察:业务流程从概念到实践
  • realloc用法
  • 智慧团建网站登录电脑版装饰工程有限公司的经营范围
  • STM32学习(MCU控制)(SPI and Flash 存储)
  • 网站推广有哪些方案pr免费模板网站
  • 轻量级HTTPSocks代理GOST: Linux编译安装
  • 有没有教做健身餐的网站wordpress菜单跳转
  • 以小白视角尝试 WPF / WinUI3 / MAUI / MAUI Blazor 构建 Windows 桌面程序
  • 网络原理-进阶