Java 基础——函数式编程
目录
本文笔记整理自黑马程序员视频 https://www.bilibili.com/video/BV1fz421C7tj/,相关资料可在该视频评论区领取。
1.函数
1.1.什么是函数?
什么是函数呢?函数即规则数学上:

例如:
| INPUT | f(x) | OUTPUT |
|---|---|---|
| 1 | ? | 1 |
| 2 | ? | 4 |
| 3 | ? | 9 |
| 4 | ? | 16 |
| 5 | ? | 25 |
| … | … | … |
- f ( x ) = x 2 f(x) = x^2 f(x)=x2 是一种规律, input 按照此规律变化为 output
- 很多规律已经由人揭示,例如 e = m ⋅ c 2 e = m \cdot c^2 e=m⋅c2
- 程序设计中更可以自己去制定规律,一旦成为规则的制定者,你就是神
1.2.函数不变性
1.2.1.什么是函数不变性?
只要输入相同,无论多少次调用,无论什么时间调用,输出相同。例如:
publicclassTestMutable{publicstaticvoidmain(String[] args){System.out.println(pray("张三"));System.out.println(pray("张三"));System.out.println(pray("张三"));}staticclassBuddha{String name;publicBuddha(String name){this.name = name;}}staticBuddha buddha =newBuddha("佛祖");staticStringpray(String person){return(person +"向["+ buddha.name +"]虔诚祈祷");}}以上 pray 的执行结果,除了参数变化外,希望函数的执行规则永远不变
张三向[佛祖]虔诚祈祷 张三向[佛祖]虔诚祈祷 张三向[佛祖]虔诚祈祷 然而,由于设计上的缺陷,函数引用了外界可变的数据,如果这么使用
buddha.name ="魔王";System.out.println(pray("张三"));结果就会是
张三向[魔王]虔诚祈祷 问题出在哪儿呢?函数的目的是除了参数能变化,其它部分都要不变,这样才能成为规则的一部分。佛祖要成为规则的一部分,也要保持不变。改正方法如下:
staticclassBuddha{finalString name;publicBuddha(String name){this.name = name;}}或
recordBuddha(String name){}注意:不是说函数不能引用外界的数据,而是它引用的数据必须也能作为规则的一部分。
1.2.2.函数与方法
方法本质上也是函数,不过方法绑定在对象之上,它是对象的个人法则。
1.2.3.不变的好处
只有不变,才能在滚滚时间洪流中屹立不倒,成为规则的一部分,例如在多线程编程中,不变意味着线程安全。
1.3.函数对象
1.3.1.函数化对象
函数本无形,也就是它代表的规则:位置固定、不能传播。若要有形,让函数的规则能够传播,需要将函数化为对象。
publicclassMyClass{staticintadd(int a,int b){return a + b;}}与
interfaceLambda{intcalculate(int a,int b);}Lambda add =(a, b)-> a + b;// 它已经变成了一个 lambda 对象区别在哪?
- 前者是纯粹的一条两数加法规则,它的位置是固定的,要使用它,需要通过 MyClass.add 找到它,然后执行;
- 而后者(add 对象)就像长了腿,它的位置是可以变化的,想去哪里就去哪里,哪里要用到这条加法规则,把它传递过去;
- 接口的目的是为了将来用它来执行函数对象,此接口中只能有一个方法定义;
函数化为对象做个比喻:
- 之前是大家要统一去西天取经;
- 现在是每个菩萨、罗汉拿着经书,入世传经;
例如:
publicclassTest{interfaceLambda{intcalculate(int a,int b);}staticclassServer{publicstaticvoidmain(String[] args)throwsIOException{ServerSocket ss =newServerSocket(8080);System.out.println("server start...");while(true){Socket s = ss.accept();Thread.ofVirtual().start(()->{try{ObjectInputStream is =newObjectInputStream(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){thrownewRuntimeException(e);}});}}}staticclassClient1{publicstaticvoidmain(String[] args)throwsIOException{try(Socket s =newSocket("127.0.0.1",8080)){Lambda lambda =(Lambda&Serializable)(a, b)-> a + b;ObjectOutputStream os =newObjectOutputStream(s.getOutputStream()); os.writeObject(lambda); os.flush();}}}staticclassClient2{publicstaticvoidmain(String[] args)throwsIOException{try(Socket s =newSocket("127.0.0.1",8080)){Lambda lambda =(Lambda&Serializable)(a, b)-> a - b;ObjectOutputStream os =newObjectOutputStream(s.getOutputStream()); os.writeObject(lambda); os.flush();}}}staticclassClient3{publicstaticvoidmain(String[] args)throwsIOException{try(Socket s =newSocket("127.0.0.1",8080)){Lambda lambda =(Lambda&Serializable)(a, b)-> a * b;ObjectOutputStream os =newObjectOutputStream(s.getOutputStream()); os.writeObject(lambda); os.flush();}}}}上面的例子做了一些简单的扩展,可以看到不同的客户端可以上传自己的计算规则
P.S.大部分文献都说 lambda 是匿名函数,但我觉得需要在这个说法上进行补充;至少在 Java 里,虽然 lambda 表达式本身不需要起名字,但得提供一个对应接口;
1.3.2.行为参数化
已知学生类定义如下:
staticclassStudent{privateString name;privateint age;privateString sex;publicStudent(String name,int age,String sex){this.name = name;this.age = age;this.sex = sex;}publicintgetAge(){return age;}publicStringgetName(){return name;}publicStringgetSex(){return sex;}@OverridepublicStringtoString(){return"Student{"+"name='"+ name +'\''+", age="+ age +",+ sex +'\''+'}';}}针对一组学生集合,筛选出男学生,下面的代码实现如何,评价一下:
publicstaticvoidmain(String[] args){List<Student> students =List.of(newStudent("张无忌",18,"男"),newStudent("杨不悔",16,"女"),newStudent("周芷若",19,"女"),newStudent("宋青书",20,"男"));System.out.println(filter(students));// 能得到 张无忌,宋青书}staticList<Student>filter(List<Student> students){List<Student> result =newArrayList<>();for(Student student : students){if(student.sex.equals("男")){ result.add(student);}}return result;}如果需求再变动一下,要求找到 18 岁以下的学生,上面代码显然不能用了,改动方法如下:
staticList<Student>filter(List<Student> students){List<Student> result =newArrayList<>();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既然它们就是不同,那么能否把它作为参数传递进来,这样处理起来不就一致了吗?
staticList<Student>filter(List<Student> students,???){List<Student> result =newArrayList<>();for(Student student : students){if(???){ result.add(student);}}return result;}它俩要判断的逻辑不同,那这两处不同的逻辑必然要用函数来表示,将来这两个函数都需要用到 student 对象来判断,都应该返回一个 boolean 结果,怎么描述函数的长相呢?
interfaceLambda{booleantest(Student student);}方法可以统一成下述代码
staticList<Student>filter(List<Student> students,Lambda lambda){List<Student> result =newArrayList<>();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 方法仍被执行了。
staticLogger logger =LogManager.getLogger();publicstaticvoidmain(String[] args){System.out.println(logger.getLevel()); logger.debug("{}",expensive());}staticStringexpensive(){System.out.println("执行耗时操作");return"结果";}改进方法 1:
if(logger.isDebugEnabled()) logger.debug("{}",expensive());显然这么做,很多类似代码都要加上这样 if 判断,很不优雅。
改进方法 2:在 debug 方法外再套一个新方法,内部逻辑大概是这样:
publicvoiddebug(finalString msg,finalSupplier<?> 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.类名::静态方法名
如何理解:
- 函数对象的逻辑部分是:调用此静态方法;
- 因此这个静态方法需要什么参数,函数对象也提供相应的参数即可;
publicclassType2Test{publicstaticvoidmain(String[] args){/* 需求:挑选出所有男性 */Stream.of(newStudent("张无忌","男"),newStudent("周芷若","女"),newStudent("宋青书","男")).filter(Type2Test::isMale).forEach(student ->System.out.println(student));}staticbooleanisMale(Student student){return student.sex.equals("男");}recordStudent(String name,String sex){}}- filter 这个高阶函数接收的函数类型 (Predicate) 是:一个 T 类型的入参,一个 boolean 的返回值,因此我们只需要给它提供一个相符合的 lambda 对象即可;
- 在方法引用
Type2Test::isMale中,isMale这个静态方法有入参 Student 对应 T,有返回值 boolean 也能对应上,所以可以直接使用;
publicinterfaceStream<T>extendsBaseStream<T,Stream<T>>{//...Stream<T>filter(Predicate<?superT> predicate);}上述代码的输出如下:
Student[name=张无忌, sex=男] Student[name=宋青书, sex=男] 2.3.2.类名::非静态方法名
如何理解:
- 函数对象的逻辑部分是:调用此非静态方法;
- 因此这个函数对象需要提供一个额外的对象参数,以便能够调用此非静态方法;
- 非静态方法的剩余参数,与函数对象的剩余参数一一对应;
例 1:
publicclassType3Test{publicstaticvoidmain(String[] args){highOrder(Student::hello);}staticvoidhighOrder(Type3 lambda){System.out.println(lambda.transfer(newStudent("张三"),"你好"));}interfaceType3{Stringtransfer(Student stu,String message);}staticclassStudent{String name;publicStudent(String name){this.name = name;}publicStringhello(String message){returnthis.name +" say: "+ message;}}}上例中函数式接口 Type3 中 transfer 的
- 参数 1
stu对应hello方法所属 Student 类的对象; - 参数 2
message对应hello方法自己的参数message; - 返回值对应着
hello方法自己的返回值 String;
输出如下:
张三 say: 你好 例 2(改写之前根据性别过滤的需求)
publicclassType2Test{publicstaticvoidmain(String[] args){/* 需求:挑选出所有男性学生 */Stream.of(newStudent("张无忌","男"),newStudent("周芷若","女"),newStudent("宋青书","男")).filter(Student::isMale).forEach(student ->System.out.println(student));}recordStudent(String name,String sex){booleanisMale(){returnthis.sex.equals("男");}}}- filter 这个高阶函数接收的函数类型 (Predicate) 是:一个 T 类型的入参,一个 boolean 的返回值,因此我们只需要给它提供一个相符合的 lambda 对象即可;
- 它的入参1 T 对应着 isMale 非静态方法的所属类型 Student;
- 它没有其它参数,isMale 方法也没有参数;
- 返回值都是 boolean;
输出如下:
Student[name=张无忌, sex=男] Student[name=宋青书, sex=男] 例 3(将学生对象仅保留学生的姓名)
publicclassType2Test{publicstaticvoidmain(String[] args){Stream.of(newStudent("张无忌","男"),newStudent("周芷若","女"),newStudent("宋青书","男")).map(Student::name).forEach(student ->System.out.println(student));}recordStudent(String name,String sex){booleanisMale(){returnthis.sex.equals("男");}}}- map 这个高阶函数接收的函数类型 (Function) 是:一个 T 类型的参数,一个 R 类型的返回值;
- 它的第一个入参 T 对应着非静态方法
name()的所属类 Student 的对象; - 它没有剩余参数,
name()方法也没有参数; - 它的返回值 R 对应着
name()方法的返回值 String;
publicinterfaceStream<T>extendsBaseStream<T,Stream<T>>{//...<R>Stream<R>map(Function<?superT,?extendsR> mapper);}输出如下:
张无忌 周芷若 宋青书 注:record类的字段会自动生成访问方法,这些方法的名字与字段名相同。例如,Student 类中的 name 字段会有一个名为name()的方法。
2.3.3.对象::非静态方法名
如何理解:
- 函数对象的逻辑部分是:调用此非静态方法;
- 因为对象已提供,所以不必作为函数对象参数的一部分;
- 非静态方法的剩余参数,与函数对象的剩余参数一一对应;
publicclassType4Test{publicstaticvoidmain(String[] args){Util util =newUtil();// 对象Stream.of(newStudent("张无忌","男"),newStudent("周芷若","女"),newStudent("宋青书","男")).filter(util::isMale).map(util::getName).forEach(student ->System.out.println(student));}recordStudent(String name,String sex){booleanisMale(){returnthis.sex.equals("男");}}staticclassUtil{booleanisMale(Student student){return student.sex.equals("男");}StringgetName(Student student){return student.name();}}}其实较为典型的一个应用就是 System.out 对象中的非静态方法,最后的输出可以修改为:
.forEach(System.out::println);这是因为:
forEach这个高阶函数接收的函数类型 (Consumer) 有一个 T 类型的参数,无返回值;- 而
System.out对象中有非静态方法void println(Object x)与之一致,因此可以将此方法化为 lambda 对象给 forEach 使用;
publicinterfaceStream<T>extendsBaseStream<T,Stream<T>>{//...voidforEach(Consumer<?superT> action);}publicclassPrintStreamextendsFilterOutputStreamimplementsAppendable,Closeable//...publicvoidprintln(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 对象。函数类型应满足:
- 参数部分与构造方法参数一致;
- 返回值类型与构造方法所在类一致;
例如:
publicclassType5Test{staticclassStudent{privatefinalString name;privatefinalint age;publicStudent(){this.name ="某人";this.age =18;}publicStudent(String name){this.name = name;this.age =18;}publicStudent(String name,int age){this.name = name;this.age = age;}@OverridepublicStringtoString(){return"Student{"+"name='"+ name +'\''+", age="+ age +'}';}}interfaceType51{Studentcreate();}interfaceType52{Studentcreate(String name);}interfaceType53{Studentcreate(String name,int age);}publicstaticvoidmain(String[] args){hiOrder((Type51)Student::new);hiOrder((Type52)Student::new);hiOrder((Type53)Student::new);}staticvoidhiOrder(Type51 creator){System.out.println(creator.create());}staticvoidhiOrder(Type52 creator){System.out.println(creator.create("张三"));}staticvoidhiOrder(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 种方法引用的特例,只能用在类内部:
publicclassType6Test{publicstaticvoidmain(String[] args){Util util =newUtilExt(); util.hiOrder(Stream.of(newStudent("张无忌","男"),newStudent("周芷若","女"),newStudent("宋青书","男")));}recordStudent(String name,String sex){}staticclassUtil{booleanisMale(Student student){return student.sex.equals("男");}booleanisFemale(Student student){return student.sex.equals("女");}voidhiOrder(Stream<Student> stream){ stream.filter(this::isMale).forEach(System.out::println);}}}输出结果如下:
Student[name=张无忌, sex=男]Student[name=宋青书, sex=男]2.3.6.super::非静态方法名
super::非静态方法名是第 2 种方法引用的特例,只能用在类内部(用在要用 super 区分重载方法时)。
publicclassType6Test{//...staticclassUtilExtextendsUtil{voidhiOrder(Stream<Student> stream){ stream.filter(super::isFemale).forEach(System.out::println);}}}2.3.7.特例
函数接口和方法引用之间,可以差一个返回值,例如
publicclassExceptionTest{publicstaticvoidmain(String[] args){Runnable task1 =ExceptionTest::print1;Runnable task2 =ExceptionTest::print2;}staticvoidprint1(){System.out.println("task1 running...");}staticintprint2(){System.out.println("task2 running...");return1;}}可以看到 Runnable 接口不需要返回值,而实际的函数对象多出的返回值也不影响使用。
2.4.闭包 (Closure)
何为闭包,闭包就是函数对象与外界变量绑定在一起,形成的整体。例如:
publicclassClosureTest1{interfaceLambda{intadd(int y);}publicstaticvoidmain(String[] args){int x =10;highOrder(y -> x + y);}staticvoidhighOrder(Lambda lambda){System.out.println(lambda.add(20));}}- 代码中的 y → x + y y \rightarrow x + y y→x+y 和 x = 10 x = 10 x=10,就形成了一个闭包;
- 可以想象成,函数对象有个背包,背包里可以装变量随身携带,将来函数对象甭管传递到多远的地方,包里总装着个 x = 10 x = 10 x=10;
- 有个限制,局部变量 x 必须是 final 或 effective final 的,effective final 意思就是,虽然没有用 final 修饰,但就像是用 final 修饰了一样,不能重新赋值,否则就语法错误;
- 意味着闭包变量,在装进包里的那一刻,就不能变化了;
- 道理也简单,为了保证函数的不变性;
- 闭包是一种给函数执行提供数据的手段,函数执行既可以使用函数入参,还可以使用闭包变量;
publicclassClosureTest2{// 闭包作用:给函数对象提供参数以外的数据publicstaticvoidmain(String[] args)throwsIOException{// 创建 10 个任务对象,并且每个任务对象给一个任务编号List<Runnable> list =newArrayList<>();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)
柯里化的作用是让函数对象分步执行(本质上是利用多个函数对象和闭包)。例如:
publicclassCarrying1Test{publicstaticvoidmain(String[] args){highOrder(a -> b -> a + b);}staticvoidhighOrder(Step1 step1){Step2 step2 = step1.exec(10);System.out.println(step2.exec(20));System.out.println(step2.exec(50));}interfaceStep1{Step2exec(int a);}interfaceStep2{intexec(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.判断语法正确性
interfaceLambda1{intop(int a,int b);}interfaceLambda2{voidop(Object obj);}Lambda1 lambda = a, b -> a - b❌Lambda1 lambda = (c, d) -> c * d✅Lambda1 lambda = (int a, b) -> a + b❌Lambda2 lambda = Object a -> System.out.println(a)❌
2.6.2.写出等价的 lambda 表达式
staticclassStudent{privateString name;publicStudent(String name){this.name = name;}publicStringgetName(){return name;}publicvoidsetName(String name){this.name = name;}@Overridepublicbooleanequals(Object o){if(this== o)returntrue;if(o ==null||getClass()!= o.getClass())returnfalse;Student student =(Student) o;returnObjects.equals(name, student.name);}@OverridepublicinthashCode(){returnObjects.hash(name);}}Math::random()->Math.random()Math::sqrt(double number)->Math.sqrt(number)Student::getName(Student stu)->stu.getName()Student::setName(Student stu, String newName) -> stu.setName(newName)Student::hashCode(Student stu) -> stu.hashCode()Student::equals(Student stu, Object o) -> stu.equals(o)
假设已有对象 Student stu = new Student("张三");
stu::getName()->stu.getName()stu::setName(String newName)->stu.setName(newName)Student::new(String name)->new Student(name)
3.Stream API
3.1.过滤
publicinterfaceStream<T>extendsBaseStream<T,Stream<T>>{//...Stream<T>filter(Predicate<?superT> predicate);}recordFruit(String cname,String name,String category,String color){}Stream.of(newFruit("草莓","Strawberry","浆果","红色"),newFruit("桑葚","Mulberry","浆果","紫色"),newFruit("杨梅","Waxberry","浆果","红色"),newFruit("核桃","Walnut","坚果","棕色"),newFruit("草莓","Peanut","坚果","棕色"),newFruit("蓝莓","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.映射
publicinterfaceStream<T>extendsBaseStream<T,Stream<T>>{//...<R>Stream<R>map(Function<?superT,?extendsR> mapper);}
.map(f -> f.cname()+"酱")3.3.降维
publicinterfaceStream<T>extendsBaseStream<T,Stream<T>>{//...<R>Stream<R>flatMap(Function<?superT,?extendsStream<?extendsR>> mapper);}例1

Stream.of(List.of(newFruit("草莓","Strawberry","浆果","红色"),newFruit("桑葚","Mulberry","浆果","紫色"),newFruit("杨梅","Waxberry","浆果","红色"),newFruit("蓝莓","Blueberry","浆果","蓝色")),List.of(newFruit("核桃","Walnut","坚果","棕色"),newFruit("草莓","Peanut","坚果","棕色"))).flatMap(Collection::stream)- 这样把坚果和浆果两个集合变成了含六个元素的水果流
例2:
Stream.of(newOrder(1,List.of(newItem(6499,1,"HUAWEI MateBook 14s"),newItem(6999,1,"HUAWEI Mate 60 Pro"),newItem(1488,1,"HUAWEI WATCH GT 4"))),newOrder(1,List.of(newItem(8999,1,"Apple MacBook Air 13"),newItem(7999,1,"Apple iPhone 15 Pro"),newItem(2999,1,"Apple Watch Series 9"))))想逐一处理每个订单中的商品
.flatMap(order -> order.items().stream())这样把一个有两个元素的订单流,变成了一个有六个元素的商品流
3.4.构建
publicinterfaceStream<T>extendsBaseStream<T,Stream<T>>{//...publicstatic<T>Stream<T>of(T... values){returnArrays.stream(values);}}根据已有的数组构建流
Arrays.stream(array)根据已有的 Collection 构建流(包括 List,Set 等)
List.of("a","b","c").stream()把一个对象变成流
Stream.of("d")把多个对象变成流
Stream.of("x","y")3.5.拼接
publicinterfaceStream<T>extendsBaseStream<T,Stream<T>>{//...publicstatic<T>Stream<T>concat(Stream<?extendsT> a,Stream<?extendsT> b){Objects.requireNonNull(a);Objects.requireNonNull(b);@SuppressWarnings("unchecked")Spliterator<T> split =newStreams.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.排序与去重
已知有数据
recordHero(String name,int strength){}Stream.of(newHero("独孤求败",100),newHero("令狐冲",90),newHero("风清扬",98),newHero("东方不败",98),newHero("方证",92),newHero("任我行",92),newHero("冲虚",90),newHero("向问天",88),newHero("不戒",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 提取器,返回一个新比较器,新比较器在原有比较器结果相等时执行新的比较逻辑
增加一个辅助方法
recordHero(String name,int strength){intnameLength(){returnthis.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())))staticComparator<Hero>step1(){return(a, b)->Integer.compare(a.strength(), b.strength());}staticComparator<Hero>step2(Comparator<Hero> step1){return(c, d)-> step1.compare(d, c);}staticComparator<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.收集
publicinterfaceStream<T>extendsBaseStream<T,Stream<T>>{//...<R>Rcollect(Supplier<R> supplier,BiConsumer<R,?superT> accumulator,BiConsumer<R,R> combiner);<R,A>Rcollect(Collector<?superT,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
publicstaticvoidmain(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)其它下游收集器
recordHero(String name,int strength){}Stream<Hero> stream =Stream.of(newHero("令狐冲",90),newHero("风清扬",98),newHero("独孤求败",100),newHero("方证",92),newHero("东方不败",98),newHero("冲虚",90),newHero("向问天",88),newHero("任我行",92),newHero("不戒",88));/* 1.mapping(x->y, dc) 需求:根据名字长度分组,分组后组内只保留他们的武力值 new Hero("令狐冲", 90) -> 90 dc 下游收集器(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=33=44=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 方法来自定义收集器,从而打印相应的日志来便于观察。
publicclassParallelExample{publicstaticvoidmain(String[] args){Stream.of(1,2,3,4).parallel().collect(Collector.of(()->{// 1.如何创建容器System.out.printf("%-12s %s%n",simple(),"create");returnnewArrayList<Integer>();},(list, x)->{// 2.如何向容器添加数据List<Integer> old =newArrayList<>(list); list.add(x);System.out.printf("%-12s %s.add(%d)=>%s%n",simple(), old, x, list);},(list1, list2)->{// 3.如何合并两个容器的数据List<Integer> old =newArrayList<>(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 // 容器需要支持并发));}privatestaticStringsimple(){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 的逻辑;
- 类和对象从哪来:运行期间动态生成;
以下面代码为例
publicclassTestLambda{publicstaticvoidmain(String[] args){test((a, b)-> a + b);}staticvoidtest(BinaryOperator<Integer> lambda){System.out.println(lambda.apply(1,2));}}执行结果
3 4.1.2.第一步:生成静态方法
如何证明?用反射
for(Method method :TestLambda.class.getDeclaredMethods()){System.out.println(method);}输出如下(去掉了包名,容易阅读):
publicstaticvoidTestLambda.main(java.lang.String[])staticvoidTestLambda.test(BinaryOperator)privatestaticjava.lang.IntegerTestLambda.lambda$main$0(Integer,Integer)- 可以看到除了我们自己写的 main 和 test 以外,多出一个名为
lambda$main$0的方法; - 这个方法是在编译期间由编译器生成的方法,是 synthetic(合成)方法;
- 它的参数、内容就是 lambda 表达式提供的参数和内容,如下面代码片段所示:
privatestaticInteger lambda$main$0(Integer a,Integer b){return a + b;}4.1.3.第二步:生成实现类字节码
如果是我自己造一个对象包含此方法,可以这么做,先创建一个类:
finalclassLambdaObjectimplementsBinaryOperator<Integer>{@OverridepublicIntegerapply(Integer a,Integer b){returnTestLambda.lambda$main$0(a, b);}}将来使用时,创建对象:
test(newLambdaObject());只不过,jvm 是在运行期间造出的这个类以及对象而已,要想查看这个类,在 JDK 21 中运行时添加虚拟机参数:
-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles 早版本 JDK 添加的参数是:
-Djdk.internal.lambda.dumpProxyClasses 若想实现在运行期间生成上述 class 字节码,有两种手段
- 一是动态代理,JDK 并没有采用这种办法来生成 Lambda 类;
- 二是用 LambdaMetaFactory,它配合 MethodHandle API 在执行时更具性能优势;
publicclassTestLambda1{publicstaticvoidmain(String[] args)throwsThrowable{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);}staticInteger lambda$main$1(Integer a,Integer b){return a + b;}staticvoidtest(BinaryOperator<Integer> lambda){System.out.println(lambda.apply(1,2));}}其中:
- “apply” 是接口方法名
- factoryType 是工厂方法长相
- interfaceMethodType 是接口方法长相
- implementsMethod 是实现方法
- implementsMethodType 是实现方法长相
- lambdaType 是实际函数对象长相
- callSite.getTarget() 实际是调用实现类的构造方法对应的 mh,最后 invoke 返回函数对象
4.2.方法引用原理
publicclassTestLambda3{publicstaticvoidmain(String[] args)throwsThrowable{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"));}staticvoidtest(Function<String,String> lambda){System.out.println(lambda.apply("Tom"));}}4.3.闭包原理
捕获基本类型变量
int c =10;test((a, b)-> a + b + c);staticvoidtest(BinaryOperator<Integer> lambda){System.out.println(lambda.apply(1,2));}生成一个带 3 个参数的方法,但它和 BinaryOperator 还差一个 int 参数
staticInteger lambda$main$1(int c,Integer a,Integer b){return a + b + c;}publicclassTestLambda2{publicstaticvoidmain(String[] args)throwsThrowable{// 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);}staticInteger lambda$main$1(int c,Integer a,Integer b){return a + b + c;}staticvoidtest(BinaryOperator<Integer> lambda){System.out.println(lambda.apply(1,2));}}不同之处
- factoryType,除了原本的接口类型之外,多了实现方法第一个参数的类型
- 产生 lambda 对象的时候,通过 invoke 把这个参数的实际值传进去
这样产生的 LambdaType 就是这样,并且生成 Lambda 对象时,c 的值被固定为 10
finalclassLambdaTypeimplementsBinaryOperator{privatefinalint c;privateTestLambda2$$Lambda(int c){this.c = c;}publicObjectapply(Object a,Object b){returnTestLambda2.lambda$main$1(this.c,(Integer)a,(Integer)b);}}捕获引用类型变量
publicclassTestLambda4{staticclassMyRef{int age;publicMyRef(int age){this.age = age;}}publicstaticvoidmain(String[] args)throwsThrowable{/*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(newMyRef(20)).invoke();test(lambda);}staticInteger lambda$main$1(MyRef c,Integer a,Integer b){return a + b + c.age;}staticvoidtest(BinaryOperator<Integer> lambda){System.out.println(lambda.apply(1,2));}}与捕获基本类型变量类似,不过
除了
callSite.getTarget().invoke(newMyRef(20));还可以
callSite.getTarget().bindTo(newMyRef(20)).invoke();4.4.Stream 构建
自定义可切分迭代器
publicclassTestSpliterator{staticclassMySpliterator<T>implementsSpliterator<T>{T[] array;int begin;int end;publicMySpliterator(T[] array,int begin,int end){this.array = array;this.begin = begin;this.end = end;}@OverridepublicbooleantryAdvance(Consumer<?superT> action){if(begin > end){returnfalse;} action.accept(array[begin++]);returntrue;}@OverridepublicSpliterator<T>trySplit(){if(estimateSize()>5){int mid =(begin + end)>>>1;MySpliterator<T> res =newMySpliterator<>(array, begin, mid);System.out.println(Thread.currentThread().getName()+"=>"+ res); begin = mid +1;return res;}returnnull;}@OverridepublicStringtoString(){returnArrays.toString(Arrays.copyOfRange(array, begin, end +1));}@OverridepubliclongestimateSize(){return end - begin +1;}@Overridepublicintcharacteristics(){returnSpliterator.SUBSIZED |Spliterator.ORDERED;}}publicstaticvoidmain(String[] args){Integer[] all =newInteger[]{1,2,3,4,5,6,7,8,9,10};MySpliterator<Integer> spliterator =newMySpliterator<>(all,0,9);StreamSupport.stream(spliterator,false).parallel().forEach(x ->System.out.println(Thread.currentThread().getName()+":"+ x));}}练习:按每次切分固定大小来实现