这篇文章是对黑马的函数式编程进行的总结,想要详细点可以去看他的视频。
函数式编程是java8推出的
将代码改写成函数式编程:先分析参数、返回值,最后改写 lambda本质就是一个函数对象,这个对象的类型就是接口,实现了接口里面唯一的抽象方法 所以接口的调用抽象方法就行
记住一句话就行:lambda表达式就是抽象方法的实现
什么是函数编程
1、什么是合格的函数?
函数必须是输入相同 ,则输出相同。如果函数使用了外部可变对象,那这个函数就不是合格的函数。 成员方法是函数,比如getName()方法,看似不同对象调用这个方法返回值是不用,其实这个方法默认有个参数this. 不用对象调用的时候this不同
2、为什么要用函数式编程?
函数相对于普通方法就是可以进行传递。 比如客户端要将规则传递给服务端,只能通过传递函数的形式,而不能通过传递方法 补充:可以传递对象过去,然后服务端调用这个对象的某个方法,但是这个前提就是服务端有这个对象的字节码文件,因为对象传过去要反序列化为对象
public class hanshu { public static void main(String[] args) { System.out.println(result.calculate(1, 2)); } static MyLambda result=(a,b)->a+b;//没有加{}的话,会自动返回表达式的计算值 //实现了MyLambda接口的calculate方法,在实际运用中,我们可以将result作为参数传递 } interface MyLambda{ int calculate(int x,int y); }
3、什么是行为参数化?
接口中只有一个抽象方法,所有的逻辑都是给这个方法的。也是调用这个方法来完成任务
通过函数作为参数传递,这样可以避免大量的重复
比如学生对象中,我们要根据条件1筛选出对应的学生,根绝条件2筛选出对应的学生。如果是以前的话,就需要编写两个方法来分别筛选,以后有更多的需求,就要编写更多的方法。
我们可以将条件作为参数传递给筛选方法,这样就能实现代码复用(注意:不是使用函数直接筛选,这样和前面的情况就没什么区别了)
//1、我们要先定义一个接口和对应的抽象方法(只能有一个),这样就能通过这个接口来调用对应的逻辑 interface Lambda { boolean test(Student student);//计算满足条件的话就返回true } //2、改写原来代码的逻辑,将条件作为参数传递过去 static List<Student> filter0(List<Student> students, Lambda lambda) { List<Student> result = new ArrayList<>(); for (Student student : students) { if (lambda.test(student)) { result.add(student); } } return result; } //3、调用方法 //需求1:筛选男性学生 System.out.println(filter0(students, student -> student.sex.equals("男"))); //也可以写成 System.out.println(filter0(students, (Student student) -> student.sex.equals("男"))); //需求2:筛选18岁以下学生 System.out.println(filter0(students, student -> student.age < 18)); // 需求3:筛选出女学生 System.out.println(filter0(students, student -> student.sex.equals("女"))); //其实lambda就是下面这种匿名内部类的简写 System.out.println(filter0(students, new Lambda() { @Override public boolean test(Student student) { if( student.sex.equals("男")){ return true; } return false; } }));
4、延迟执行
我们想实现日志级别为debug才执行expensive方法 logger.debug("{}", expensive()); // expensive() 立刻执行 logger.debug("{}", () -> expensive()); // 函数对象使得 expensive 延迟执行,底层做了判断
package com.itheima.day1; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.apache.logging.log4j.core.config.Configurator; import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; // 函数对象好处2:延迟执行 public class Sample7 { static Logger logger = init(Level.INFO); public static void main(String[] args) { /*if (logger.isDebugEnabled()) { logger.debug("{}", expensive()); }*/ logger.debug("{}", expensive()); // expensive() 立刻执行 logger.debug("{}", () -> expensive()); // 函数对象使得 expensive 延迟执行 } static String expensive() { System.out.println("执行耗时操作..."); return "日志"; } static Logger init(Level level) { ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder() .setStatusLevel(Level.ERROR) .setConfigurationName("BuilderTest"); AppenderComponentBuilder appender = builder.newAppender("Stdout", "CONSOLE") .addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT) .add(builder.newLayout("PatternLayout").addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable")); builder.add(appender) .add(builder.newRootLogger(level).add(builder.newAppenderRef("Stdout"))); Configurator.initialize(builder.build()); return LogManager.getLogger(); } }
函数编程语法
1、lambda语法
要将lambda看成一个对象,这个对象的类型就是自定义的接口
省略()的前提是不能加类型声明 lambda 和方法引用只是相关逻辑(函数对象) 要真正去执行里面的抽象方法才会生效,有些提供好的方法底层都是调用对应的抽象方法
2、方法引用
不需要显式地写出lambda表达式 就能赋值接口对象现有的逻辑 对应的参数是调用的时候才传递的
比如这样 public class hanshu { public static void main(String[] args) { MyLambda myLambda=Math::max;//将具体的逻辑赋值 //等同于 MyLambda myLambda=(a,b)->a+b; System.out.println(myLambda.test(1, 2)); } } interface MyLambda{ int test(int a,int b ); }
使用对象的方法引用就不需要传递未知的参数了
使用类的方法引用就需要传递未知的参数
(1)类名::静态方法
逻辑部分就是调用这个静态方法 参数部分就得看逻辑部分要用到哪些参数
Student::静态方法
要传递对应的对象参数
public class hanshu { public static void main(String[] args) { Stream.of( new Student("张三","男"), new Student("李四","女"), new Student("王五","男") ).filter(hanshu::abc).forEach( System.out::println ); /*使用lambda的情况 Stream.of( new Student("张三","男"), new Student("李四","女"), new Student("王五","男") ).filter((stu)->hanshu.abc(stu)).forEach( stu -> System.out.println(stu) ); */ } public static boolean abc(Student stu){ if(stu.getSex().equals("男")){ return true; } return false; } } class Student{ private String name; private String sex; //略 }
(2)类名::非静态方法
不用太关注类名后面加的是静态方法还是非静态方法,方法引用都是类名::方法名 只不过lambda表达式中逻辑部分 非静态方法是对象调用 ,静态方法是类调用(都是想想就知道的)
Student::非静态方法
要传递对应的对象参数
方法引用
Student::abc
只适用于无参的静态方法或者实例方法调用时是通过对象来引用的。
Stream.of( new Student("张三","男"), new Student("李四","女"), new Student("王五","男") ).filter(Student::isMale).forEach( stu -> System.out.println(stu) ); class Student{ private String name; private String sex; public boolean isMale(){ return this.getSex().equals("男"); } //略 }
(3)对象::非静态方法
对比上面的类名::非静态方法,未知多少参数就写多少参数,而(2)要多出一个参数,因为他不知道是哪个对象要用,而(3)他已经知道是哪一个对象调用的方法
util util=new util(); Stream.of( new Student("张三","男"), new Student("李四","女"), new Student("王五","男") ).filter(util::isMale)//对象::非静态方法 //.filter(Student::isMale) 类名::非静态方法 // .map((stu)->stu.getName())//将流中的元素转化为另一个类型 .map(Student::getName)//类名::非静态 .forEach( stu -> System.out.println(stu) ); class util{ public boolean isMale(Student stu){ return stu.getSex().equals("男"); } }
(4)类名::new
得看用什么函数对象接收 只有调用了函数对象的抽象方法才会真正的执行对应的方法
Supplier<Student> s= Student::new; System.out.println(s.get()); Function<String,Student> f=Student::new; System.out.println(f.apply("李四")); BiFunction<String,String,Student> b=Student::new; System.out.println(b.apply("王五", "男")); class Student{ public Student() { this.name="张三"; this.sex="男"; } public Student(String name){ this.name=name; } public Student(String name, String sex) { this.name = name; this.sex = sex; } }
(5)this::非静态方法、super::非静态方法
public class hanshu { public static void main(String[] args) { Util2 util=new Util2(); util.hiOrder( Stream.of( new Student("张三","男"), new Student("李四","女"), new Student("王五","男") )); } } class Util{ public boolean isMale(Student stu){ return stu.getSex().equals("男"); } public boolean isFemale(Student stu){ return stu.getSex().equals("女"); } public void hiOrder(Stream<Student> stream){ stream.filter(this::isMale)//使用this::非静态方法 //.filter( stu->this.isMale(stu))//使用lambda .forEach(System.out::println); } } class Util2 extends Util{ public void hiOrder(Stream<Student> stream){ stream.filter(super::isFemale)//super::非静态方法 //.filter( stu->super.isMale(stu))//使用lambda .forEach(System.out::println); } }
没有返回值的函数对象接收有返回值的方法引用
3、函数接口
@FunctionalInterface 加载接口上可以用来检查这个接口是否满足规范
自定义接口
参数类型、个数一致,返回值一致可以归为同一类,可以使用同一个接口
这样下来,要定义的接口会很多,我们可以使用泛型优化,比如只是返回值不同的,可以使用同一个接口, 只是参数类型不同,也可以使用同一个接口(只要参数个数一样就行)
jdk提供的接口
jdk提供了一些接口我们可以直接拿来用,不用自己定义这个函数接口
1、Predicate<T> --千万注意要指明参数类型 功能:接收一个参数,返回 boolean 类型的结果。 2、Consumer<T> 功能:接收一个参数,没有返回值。 3、Supplier<T> 功能:不接收参数,返回一个结果。 4、Function<T, R> 功能:接收一个参数,返回一个结果。
public class hanshu { public static void main(String[] args) { filter(List.of(1,3,4,5,6),(Integer i)->(i&1)==1); } static void filter(List<Integer> list, Predicate<Integer> predicate){ ArrayList arrayList=new ArrayList(); for (Integer i : list) { if(predicate.test(i)){ arrayList.add(i); } } System.out.println(arrayList); } }
命名规则
注意这三个接口 Consumer 有参无返回值 Supplier 无参 有返回值 Function 有参 有返回值
4、闭包和柯里化
(1)闭包
下面的x并没有在lambda表达式的参数中,但是运行没问题,因为x和y已经绑定在自定义的Lambda对象中了 这就是闭包
这里的x前面要是final或者effective final(外部没有改变他),也就是说不能改变他的值
但是如果是个对象,他的属性是可以改变的,这种设计与函数编程的闭包相违背了
这里也就解释了为什么在多线程的runnable中不能使用变化的变量值 for (int i = 0; i < 10; i++) { int k=i; new Thread(()->{ System.out.println(k);//直接使用i编译不通过,因为闭包机制 }).start(); }
(2)柯里化
使用(a,b)->a+b可以完成两个数相加的操作 如果只有一个参数要怎么实现呢?
a->函数对象 b->a+b
在之前我们能使用这种方式轻松实现两数相加 public static void main(String[] args) { // (a,b)->a+b //(a)->a+b add result= (a,b)->a+b; System.out.println(result.op(10, 20)); } @FunctionalInterface interface add{ int op(int a,int b); } 现在我们只接收一个参数,要怎么完成两数相加呢,需要用到闭包,将之前传进来的参数进行绑定
//真正执行相加逻辑的是add2的op2方法,add1中op1返回的是add2函数对象,而这个add2函数对象返回 //真正的结果,这就相当于实现了返回最终的结果 public class test { public static void main(String[] args) { // (a,b)->a+b //(a)->a+b add1 result= (a)->(b)->a+b; add2 add2 = result.op1(3); System.out.println(add2.op2(4)); //也可以写成这样System.out.println(result.op1(3).op2(4)); } @FunctionalInterface interface add1{ add2 op1(int a); } @FunctionalInterface interface add2{ int op2(int b); } }
模拟等待数据,最后进行汇合 从逻辑上看,step1() 并不直接需要 step2() 和 step3() 的数据,而是分阶段进行处理。更具体地解释,step1() 是第一步,它负责返回一个 Fb 对象,绑定了第一个列表数据 [1, 2, 3]。接着,step2() 再接收 step1() 的返回结果,并处理第二个列表数据 [4, 5, 6]。最后,step3() 接收 step2() 的结果,并处理最终的列表 [7, 8, 9]。
public class test { public static void main(String[] args) { step3( step2( step1())); } @FunctionalInterface interface Fa{ Fb op(List<Integer> a); } @FunctionalInterface interface Fb{ Fc op(List<Integer> b); } @FunctionalInterface interface Fc{ List<Integer> op(List<Integer> c); } static Fb step1(){ List<Integer> x = List.of(1, 2, 3); Fa fa=a->b->c->{ ArrayList list=new ArrayList(); list.addAll(a); list.addAll(b); list.addAll(c); return list; }; return fa.op(x);//收集1,2,3的数据 } static Fc step2(Fb fb){ List<Integer> x = List.of(4,5,6); return fb.op(x); } static void step3(Fc fc){ List<Integer> x = List.of(7,8,9); List<Integer> result = fc.op(x); System.out.println(result); } }
step1() 中的这段代码: Fa fa = a -> b -> c -> { ArrayList list = new ArrayList(); list.addAll(a); list.addAll(b); list.addAll(c); return list; }; 确实是返回一个逻辑,它定义了一个 柯里化的函数链,用于逐步接收三个列表,并将它们合并成一个新的列表。
5、高阶函数
在之前的代码中,step2、3都使用了其他函数对象,所以都是高阶函数
下面几个小点只是举例高阶函数的使用
(1)内循环
下面的test函数就是高阶函数,他用到了其他的函数对象 public class test { public static void main(String[] args) { //调用高阶函数完成调用操作 test( List.of(1,2,3,4,5,6),(val)-> System.out.println(val)); } static void test(List<Integer> list, Consumer<Integer> consumer){ ListIterator<Integer> iterator = list.listIterator(list.size()); while(iterator.hasPrevious()){ Integer value = iterator.previous(); consumer.accept(value); } } }
(2)二叉树
public class C02BinaryTree { public record TreeNode(int value, TreeNode left, TreeNode right) { public String toString() { return "%d ".formatted(value); } } enum Type { PRE, IN, POST } public static void traversal2(TreeNode root, Type type, Consumer<TreeNode> consumer) { if (root == null) { return; } // 前序处理值 if (type == Type.PRE) { consumer.accept(root);//其实就是打印操作 根据type的类型判断要在哪个位置打印 } traversal2(root.left, type, consumer); // 中序处理值 if (type == Type.IN) { consumer.accept(root); } traversal2(root.right, type, consumer); // 后序处理值 if (type == Type.POST) { consumer.accept(root); } } public static void main(String[] args) { /* 1 / \ 2 3 / / \ 4 5 6 前序 1 2 4 3 5 6 值左右 中序 4 2 1 5 3 6 左值右 后序 4 2 5 6 3 1 左右值 */ TreeNode root = new TreeNode(1, new TreeNode(2, new TreeNode(4, null, null), null ), new TreeNode(3, new TreeNode(5, null, null), new TreeNode(6, null, null) ) ); traversal2(root, Type.PRE, System.out::print); System.out.println(); traversal2(root, Type.IN, System.out::print); System.out.println(); traversal2(root, Type.POST, System.out::print); System.out.println(); } }
(3)简单流
①基础实现
filter 、of、map、forEach方法的实现
public class SimpleStream <T>{ public static void main(String[] args) { List<Integer> list = List.of(1, 2, 3, 4, 5); //收集为普通list SimpleStream.of(list) .filter(x -> (x & 1) == 1) .map(x->x*x) //.map(x -> String.valueOf(x)) .forEach(System.out::println); } public void forEach(Consumer<T> consumer){ for (T t : collection) { consumer.accept(t); } } public <U> SimpleStream<U> map(Function<T,U> function){ //将T类型的Stream转化为U类型 List<U> result = new ArrayList<>(); for (T t : collection) { U u = function.apply(t); result.add(u); } return new SimpleStream<>(result); } public static<T> SimpleStream<T> of(Collection<T> collection){//泛型方法的返回值声明前面要加上<所有用到的> //返回这样一个对象 return new SimpleStream<T>(collection); } private Collection<T> collection; public SimpleStream(Collection<T> collection){ this.collection=collection; } public SimpleStream<T> filter(Predicate<T> predicate){ //将集合对象过滤,过滤完返回一个新的集合对象 List<T> result = new ArrayList<>(); for (T t : collection) { if (predicate.test(t)) { //满足条件的元素 result.add(t); } } return new SimpleStream<>(result); } }
②reduce
reduce方法
//第一个参数是初始值要自己传,比如下面是求list中的最大值(传第一个元素或者最小整数) System.out.println(SimpleStream.of(list) .reduce(list.get(0), Integer::max)); public T reduce(T o,BinaryOperator<T> binaryOperator){ T p=o;//初始值 for (T t : collection) { p= binaryOperator.apply(t,p);//将每一次的结果重新赋值给p } return p; }
③collect
public <C> C collect(Supplier<C> supplier,BiConsumer<C,T> biConsumer){ C c = supplier.get();//创建要返回的容器 for (T t : collection) {//将元素中的元素收集到容器C中,循环执行传过来的添加逻辑 biConsumer.accept(c,t);//第一个参数是容器名 第二个参数是要添加的元素 } return c; }
正常收集
HashSet<Object> collect = SimpleStream.of(list).collect(HashSet::new, HashSet::add);
收集成字符串
StringBuilder stringbuilder = SimpleStream.of(list).collect(StringBuilder::new, StringBuilder::append);
收集成字符串+分隔符
//收集为字符串中间还有分隔符 在collect的第一个参数不能用方法引用(因为没有空参的方法) 第二个参数t不是string类型只能用lambda表达式 StringJoiner collect = SimpleStream.of(list).collect(() -> new StringJoiner(","), (joiner, t) -> joiner.add(String.valueOf(t)));
收集成HashMap
HashMap<Integer, Integer> collect = SimpleStream.of(list).collect(HashMap::new, (HashMap<Integer, Integer> map, Integer t) -> { Integer i = map.get(t); if (i == null) { map.put(t, 1); } else { map.put(t, ++i); } }); System.out.println(collect); 简化 HashMap<Integer, Integer> collect = SimpleStream.of(list).collect(HashMap::new, ( map, t) -> { Integer i = map.get(t); if (i == null) { map.put(t, 1); } else { map.put(t, ++i); } }); System.out.println(collect); 再简化 t不存在 创建新的AtomicInteger 并自增 t存在 返回已经存在的AtomicInteger 并自增 HashMap<Integer, AtomicInteger> collect = SimpleStream.of(list).collect(HashMap::new, (map, t) ->map.computeIfAbsent(t,k->new AtomicInteger()).getAndIncrement()); System.out.println(collect);
6、Stream
Collection以及他的子接口都能用这个Stream
1、降维(flatMap)
降维前:坚果和浆果是分开打印的,因为是两个集合
降维后:一起打印,都是同一个流 使用 .flatMap(list->list.stream())
对于二维数组来说,使用Arrays.stream 降维前:
降维后:
2、构建(创建Stream流)
public static void main(String[] args) { //1、集合构建 Set.of(1,2,3).stream().forEach(System.out::println); Map.of("name","张三","age",18).entrySet().stream().forEach(System.out::println); //2、数组构建 int[] arr={1,2,3,4,5}; Arrays.stream(arr).forEach(System.out::println); //3、对象构建 Stream.of(1,2,3,4).forEach(System.out::println); }
3、合并和截取
合并
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5); Stream<Integer> stream2 = Stream.of(6,7); Stream.concat(stream1,stream2).forEach(System.out::println);
截取
①skip
skip跳过前n个元素,保留剩下的 Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5); stream1.skip(3).forEach(System.out::println);//打印4 5
②limit
limit保留前n个元素,放弃剩下的 Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5); stream1.limit(3).forEach(System.out::println);
使用skip配合limit能截取中间的元素
③takewhile
条件成立保留 一旦条件不成立剩下的都不要了(即使后面还有满足条件的)
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 1); stream1.takeWhile(x->x<3).forEach(System.out::println);//打印 1 2
④dropWhile
条件成立舍弃,一旦条件不成立保留剩下的(即使后面还有满足条件的)
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 1); stream1.dropWhile(x->x<3).forEach(System.out::println);//打印3 4 1
4、生成(不用现有数据生成Stream对象)
不需要现有的数据就能成功stream流对象
public static void main(String[] args) { //范围生成 IntStream.range(1,10).forEach(x-> System.out.print(x+" ")); System.out.println(); IntStream.rangeClosed(1,9).forEach(x-> System.out.print(x+" ")); System.out.println(); //根据上一个生成的元素 IntStream.iterate(1,x->x+2).limit(10).forEach(x-> System.out.print(x+" ")); System.out.println(); IntStream.iterate(1,x->x<5,x->x+2).forEach(x-> System.out.print(x+" ")); //不依赖上一个参数 IntStream.generate(()-> ThreadLocalRandom.current().nextInt(100)).limit(5).forEach(x-> System.out.print(x+" ")); }
如果只是想生成随机数可以使用,不一定要用流
ThreadLocalRandom.current().ints(5,1,100).forEach(x-> System.out.print(x+" ")); (1~99)
5、查找与判断
查找
如果找不到满足的元素,会返回空optional对象,可以发配.orElse使用(指定找不到的时候返回默认值)
加上orElse就直接展示数值了
其实findAny在这种情况和findFirst是一样的,他主要用于并发流才会任意一个结果
判断
//1、anyMatch:任意一个满足返回true System.out.println(IntStream.of(1, 2, 3, 4).anyMatch(x -> ((x & 1) == 0))); //2、allMatch:全都满足返回true System.out.println(IntStream.of(1, 2, 3, 4).allMatch(x -> ((x & 1) == 0))); //3、noneMatch:全都不满足返回true System.out.println(IntStream.of(1, 2, 3, 4).noneMatch(x -> ((x & 1) == 0)));
6、去重和排序
去重 distinct
IntStream intStream = IntStream.of(1, 2, 3, 4, 1, 2, 3); intStream.distinct().forEach(System.out::println);
排序 sorted
整数排序
intStream.sorted().forEach(System.out::println);
对象排序
Comparator接口中
参数顺序与计算顺序一致(
a - b
):升序。参数顺序与计算顺序相反(
b - a
):降序
这种是最简便的 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) ) .sorted((a,b)->b.strength-a.strength) //后面-前面是降序 .forEach(System.out::println); 也可以用Integer现成的比较方法 .sorted((a, b) -> Integer.compare(a.strength(), b.strength())) 也可以用比较器(默认升序) .sorted(Comparator.comparingInt(h->h.strength())) 调用reversed 反序 .sorted(Comparator.comparingInt(Hero::strength).reversed()) .sorted(Comparator.comparingInt(Hero::strength).reversed().thenComparingInt(h->h.name().length())) // 按武力降序,武力相等的按名字长度升序
7、化简(reduce) 两两合并
1、求武力最高的 hero Optional<Hero> reduce = stream.reduce((h1, h2) -> h1.strength > h2.strength ? h1 : h2); System.out.println(reduce); 给一个默认值,如果流中没有元素就返回这个默认的。 Hero result = stream.reduce(new Hero("-", -1), (h1, h2) -> h1.strength() > h2.strength() ? h1 : h2); System.out.println(result);
2、求高手总数 这里要先将流里面的对象转化为整数 System.out.println(stream.map(h -> 1).reduce(0,(a, b) -> a + b)); 其实stream提供了简便的方法 System.out.println(stream.count()); 底层实现和上面的方法类似
8、收集(collect)
第一参数 (Supplier<A> supplier): 提供一个新的结果容器(例如 ArrayList::new),用于收集结果。 第二参数 (BiConsumer<A, T> accumulator): 将流中的每个元素添加到结果容器中。 第三参数 (BinaryOperator<A> combiner): 合并两个结果容器,用于在并行流中合并不同线程的结果。
收集到list
ArrayList<Object> collect = stream.collect(ArrayList::new, ArrayList::add, (a, b) -> { }); System.out.println(collect);
收集到set
HashSet<Object> collect = stream.collect(HashSet::new, Set::add, (a, b) -> { }); System.out.println(collect);
收集到 Map
Map<String, Integer> result = stream.collect(HashMap::new, (map,x)->map.put(x, 1), (a, b) -> { });
收集字符串容器
StringBuilder sb = stream.collect(StringBuilder::new, StringBuilder::append, (a,b)->{}); System.out.println(sb); StringJoiner sb = stream.collect(()->new StringJoiner(","), StringJoiner::add, (a,b)->{}); System.out.println(sb);
收集器
Stream<String> stream = Stream.of("令狐冲", "风清扬", "独孤求败", "方证", "东方不败", "冲虚", "向问天", "任我行", "不戒");
收集到 List List<String> result = stream.collect(Collectors.toList()); 收集到 Set Set<String> result = stream.collect(Collectors.toSet()); 收集到 StringBuilder String result = stream.collect(Collectors.joining()); 收集到 StringJoiner String result = stream.collect(Collectors.joining(",")); 收集到 Map--使用toMap(不常用) Map<String, Integer> map = stream.collect(Collectors.toMap(x -> x, x -> 1)); 使用groupingBy收集Map 统计名字长度相同的人物(使用字符串收集,逗号分隔) 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); } 这种也可以(将名字一样长的人物放入同一个List) Map<Integer, List<String>> result = stream.collect(Collectors.groupingBy(x -> x.length(), Collectors.toList())); for (Map.Entry<Integer, List<String>> integerListEntry : result.entrySet()) { List<String> value = integerListEntry.getValue(); System.out.println(value); }
下游收集器
stream
的下游收集器通常是与Collectors.groupingBy
配合使用的。groupingBy的第一个参数是分组规则
在下面代码中Collectors.joining(",") 、Collectors.toList()就是下游收集器
使用groupingBy收集Map 统计名字长度相同的人物(使用字符串收集,逗号分隔) 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); } 这种也可以(将名字一样长的人物放入同一个List) Map<Integer, List<String>> result = stream.collect(Collectors.groupingBy(x -> x.length(), Collectors.toList())); for (Map.Entry<Integer, List<String>> integerListEntry : result.entrySet()) { List<String> value = integerListEntry.getValue(); System.out.println(value); }
mapping
映射关系
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) ); Map<Integer, List<Hero>> collect1 = stream.collect(groupingBy(x -> x.name.length(), toList())); for (Map.Entry<Integer, List<Hero>> integerListEntry : collect1.entrySet()) { System.out.println(integerListEntry.getKey()+":"+integerListEntry.getValue()); } 这样得到的是人物的集合,我们想要的是名字长度一样的武力值
我们可以使用 第一个参数是映射规则 第二个参数是下游收集器
Map<Integer, List<Integer>> collect = stream.collect(groupingBy(x -> x.name.length(), mapping(x -> x.name.length(), toList()))); for (Map.Entry<Integer, List<Integer>> integerListEntry : collect.entrySet()) { System.out.println(integerListEntry.getKey() + ":" + integerListEntry.getValue()); }
filtering
边过滤边收集
Map<Integer, List<Hero>> collect = stream.collect(groupingBy(h -> h.name.length(), filtering(h -> h.strength() > 90, toList()))); for (Map.Entry<Integer, List<Hero>> integerListEntry : collect.entrySet()) { System.out.println(integerListEntry); }
其实可以使用先过滤再收集的方式替代,注:mapping就不行,因为先调用map转化的时候,他的一些数据就丢失了比如名字。
Map<Integer, List<Hero>> collect = stream.filter(x -> x.strength > 90).collect(groupingBy(h -> h.name.length(), toList())); for (Map.Entry<Integer, List<Hero>> integerListEntry : collect.entrySet()) { System.out.println(integerListEntry); }
flatMapping
将数据数据打散,比如令狐冲分为令 狐 冲
知识铺垫: 调用chars的mapToObj方法将ASCII码转化为具体的汉字 "令狐冲".chars();的返回值是IntStream; "令狐冲".chars().mapToObj(Character::toString).forEach(System.out::println)
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>> integerListEntry : collect.entrySet()) { System.out.println(integerListEntry); }
其他收集器
①统计个数
Map<Integer, Long> collect = stream.collect(groupingBy(h -> h.name().length(), counting())); for (Map.Entry<Integer, Long> integerLongEntry : collect.entrySet()) { System.out.println(integerLongEntry); }
左边是名字长度 右边是个数
②最大值、最小值
Map<Integer, Optional<Hero>> collect = stream.collect(groupingBy(h -> h.name().length(), maxBy(Comparator.comparingInt(x -> x.strength)))); for (Map.Entry<Integer, Optional<Hero>> integerOptionalEntry : collect.entrySet()) { System.out.println(integerOptionalEntry); }
③求总和
Map<Integer, Integer> collect = stream.collect(groupingBy(h -> h.name().length(), summingInt(x -> x.strength))); for (Map.Entry<Integer, Integer> integerIntegerEntry : collect.entrySet()) { System.out.println(integerIntegerEntry); }
9、基本流
补充
特性
一次使用
两次调用forEach就是报错
指的是终结操作
collect() forEach() reduce() count() anyMatch()
两类操作
中间操作:lazy
终结操作: eager
中间操作如map filter 调用之后不会立马执行 而是等待 终结操作如forEach 调用之后会立即执行 并执行中间操作
总结
7、并行流
调用.parallel()方法将流变成并行流,这样在底层是多线程完成操作.内存占用很大
数据量大的时候才建议使用并行流 线程数和cpu能处理的线程数有关 收尾的意义:做一些转换比如数据集合转换为不可变 return Collections.unmodifiableCollection(list)
并行流是否有线程安全问题:没有 因为每一个线程都有自己的arraylist,线程安全问题是多个线程操作同一个数据导致的 收集阶段:每一个线程都有自己的集合 合并阶段:只有一个线程进行合并两次收集的数据,比如线程A收集1,线程B收集2 合并阶段,线程A会结束,将结果给线程B进行合并,所以没有线程安全问题
(1)特性
是否需要收尾(默认收尾) 是否需要保证顺序(默认保证) 容器是否支持并发(默认不需要支持)
这三个特性可以加在of的后几个参数 Collector.Characteristics.IDENTITY_FINISH // 不需要收尾 Collector.Characteristics.UNORDERED // 不需要保证顺序 Collector.Characteristics.CONCURRENT // 容器需要支持并发
使用这两个特性配合使用,将创建的集合换成线程安全(支持并发的集合,如Vector)就不会每个线程都创建集合对象了 Collector.Characteristics.UNORDERED // 不需要保证顺序 Collector.Characteristics.CONCURRENT // 容器需要支持并发 如果是并发度高的不建议使用这种方式,性能比较差
注意:如果用流进行计算的话,使用基本流 如IntStream
并行流配合并发容器在大数据量效率好
Stream的应用之一 进行统计分析,可以看我这篇文章