Java 基础——函数式编程

Java 基础——函数式编程

目录

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

1.函数

1.1.什么是函数?

什么是函数呢?函数即规则数学上:

在这里插入图片描述


例如:

INPUTf(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);}
  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 表达式

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);}}
  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.过滤

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

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

Read more

figma + claude + weavy AI :从会用到用好

figma + claude + weavy AI :从会用到用好

Google ai studio + figma + claude.ai + cosmos + Design with Weavy AI 这套头脑风暴工具看完后,你一定可从其中悟出独特、见解,并为之惊讶。我们不需要自己动手去建房子,我们可以借助不同的工具,去找找灵感,为自己创造东西,自然而然的知道自己的感受,和想要的感受。 1 / GoogleAIStudio 端到端 制定原型 GoogleAIStudio非常好,因为它能端到端完成。然后我发现Gemini在界面设计上真的很厉害!(本次以开发一款音乐日记讲述全流程)。 2 / claude.ai 制定品牌指南 (生成品牌指南 guidelines ) 先谈谈设计思想。 就像电影和电视剧有开头、中间、结尾一样,我们现在还不太在意中间和结尾。用户他们不应该觉得我们在抢他们的注意力,或者强迫他们。用户更不愿意看到一堆广告和各种乱七八糟的东西。 很多人,觉得品牌指南听起来很像企业用语,但我认为如claude、gemin这些头脑风暴工具一定能帮助我们找到想要的点。利用claude制定品牌指南,自己想要什么,我们可以看看这个,

By Ne0inhk
2026最新保姆级教程:手把手教你零基础安装与配置本地 AI 智能体 OpenClaw

2026最新保姆级教程:手把手教你零基础安装与配置本地 AI 智能体 OpenClaw

文章目录 * 前言 * 一、下载并安装 OpenClaw * 二、启动配置向导与绑定 AI 大脑 * 1. 启动向导 * 2. 确认账户类型 * 3. 选择快速入门模式 * 4. 选择大模型 (AI 大脑) * 5. 选择 API 接口区域 * 6. 填入你的专属 API Key * 三、连接通讯渠道 (Telegram) * 1. 选择 Telegram * 2. 绑定机器人的 Token * 第四步:安装扩展插件与重启服务 * 1. 技能插件 (Skills) * 2. 附加功能 (Hooks) * 3. 重启并应用配置 * 第五步:设备安全授权与最终测试 (见证奇迹!) * 1.

By Ne0inhk
今日AI榜单速览(GitHub Trending AI Top3)

今日AI榜单速览(GitHub Trending AI Top3)

🔥 个人主页:杨利杰YJlio❄️ 个人专栏:《Sysinternals实战教程》《Windows PowerShell 实战》《WINDOWS教程》《IOS教程》《微信助手》《锤子助手》《Python》《Kali Linux》《那些年未解决的Windows疑难杂症》🌟 让复杂的事情更简单,让重复的工作自动化 今日AI热榜 * 1 1 今日榜单速览(GitHub Trending AI Top3) * 2 2 ruvnet / RuView:WiFi DensePose 的“无线透视”路线 * 2 我的一句话总结 * 2 为什么今天它能冲到第一? * 2 图:它的可视化界面长这样(很直观) * 2 我如何最快验证(不折腾工具链) * 3 3 K-Dense-AI / claude-scientific-skills:给

By Ne0inhk
【智体OS】ibbot智体机灵 V1.0:你的手机AI超脑,一句话开启智体时代————终将打败OpenClaw的国产开源项目

【智体OS】ibbot智体机灵 V1.0:你的手机AI超脑,一句话开启智体时代————终将打败OpenClaw的国产开源项目

【智体OS】ibbot智体机灵 V1.0:你的手机AI超脑,一句话开启智体时代————终将打败OpenClaw的国产开源项目 发布日期:2026年2月12日 核心定位: 基于dtns.os V5.0的下一代移动端AI智能体平台 🚀 产品宣言:告别笨重,拥抱手机里的AI工作站 还在为OpenClaw这类方案需要额外购置硬件、部署复杂而头疼吗?时代变了!ibbot智体机灵将强大的AI智能体引擎直接塞进你的安卓手机,实现 “零硬件成本、开机即用、揣兜就走” 的终极生产力形态。我们不是另一个AI工具,而是你口袋里的 “AI副驾” 和 “赚钱工作站”。 ✨ 核心亮点:为什么ibbot是颠覆者? 1. 极致轻量与便携 * 部署平台:你正在使用的安卓手机/平板。 * 硬件成本:¥0,充分利用现有设备,无需Mac mini或云服务器。 * 便携性:你的手机就是完整的工作站,灵感随时捕捉,任务随地处理。 2. 一句话创建万物 基于dtns.

By Ne0inhk