函数式编程笔记(Stream、方法引用、Lambda)

这篇文章是对黑马的函数式编程进行的总结,想要详细点可以去看他的视频。

函数式编程是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的应用之一 进行统计分析,可以看我这篇文章

Stream的应用之一 进行统计分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值