Spring Webflux 响应式编程 (一) - lambda表达式与stream流编程

springboot2 已经发布,其中最亮眼的非webflux响应式编程莫属了!响应式的weblfux可以支持高吞吐量,意味着使用相同的资源可以处理更加多的请求,毫无疑问将会成为未来技术的趋势,是必学的技术!

很多人都看过相关的入门教程,但看完之后总觉得很迷糊,知其然不知道其所以然

直接跳到运行在jdk8上的webflux,跨度太大,迷惑是在所难免的

推荐学习途径如下:

先学习jdk8的lambda表达式和stream流编程,了解函数式编程的知识点和思想,接着学习jdk9的响应式流flux,理解响应式流概念,理解背压和实现机制。这2者学好之后,很容易理解webflux的基石reactor,再学习webflux就水到渠成了!

学习技术如果不了解原理,知识点需要死记硬背,而了解了底层机制之后,不但不需要死记硬背,还可以把自己的技术点连成面融会贯通,很容易举一反三,知识点也不会忘记,也能和别人扯扯技术的底层实现了

第一章 lambda表达式

第1集 函数式编程/lambda表达式

1.1 什么是函数式编程

首先你要知道函数式编程是个什么东西,他其实是一种编程范式(方法论),范式就是一个例子一个样板。但其实编程范式有的时候常常也包含着一些编程思想在里面,举个例子编程范式就是武术中的剑招,拳术,棍法什么的,往往后面还蕴含着什么内功心法,武学思想一些东西。当然也不至于那么缥缈,就是大家在实际工作经验教训和理论方法结合的过程中总结出来的。

函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。

学习目标:熟练使用stream流api和lambda表达式,以及有流相关的编程思想并能够运用到工作中即可,不需要太纠结函数式编程的概念

命令式编程:告诉程序应该怎样做

函数式编程:告诉程序你要做什么,要实现怎么样的功能,不需要关注实现的细节

参考链接:

https://zhuanlan.zhihu.com/p/88461899

代码示例,求最小值

package lambda;

import java.util.stream.IntStream;

public class MinDemo {

    public static void main(String[] args) {
        // 原实现方式
        int[] nums = {33, 55, -55, 90, -666, 90};
        int min = Integer.MAX_VALUE;
        for (int i : nums) {
            if (i < min) {
                min = i;
            }
        }

        System.out.println(min);

        // jdk8
        int min2 = IntStream.of(nums).parallel().min().getAsInt();
        System.out.println(min2);
    }
}

代码示例,多线程

package lambda;

public class ThreadDemo {

    public static void main(String[] args) {
    	// 原实现方式
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("ok");
            }
        }).start();
        
        // jdk8 lambda
        new Thread(() -> System.out.println("ok")).start();
    }
}

1.2 什么是lamda表达式

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。


 

第2集 lambda表达式讲解

2.1 将lambda表达式拆成变量进行详解

package lambda;

public class ThreadDemo {

    public static void main(String[] args) {
        Object target = new Runnable() {
            @Override
            public void run() {
                System.out.println("ok");
            }
        };
        new Thread((Runnable) target).start();

        // jdk8 lambda
        //返回一个接口,可以使用强转的方式(不过我觉得应该没人这么用)
        Object target2 = (Runnable)() -> System.out.println("ok");
        Runnable target3 = () -> System.out.println("ok");
        System.out.println(target2 == target3); //false
        new Thread((Runnable) target2).start();
    }
}

2.2 lambda表达式带参数写法

package lambda;


interface Interface1 {
    int doubleNum(int i);
}

public class LambdaDemo1 {

    public static void main(String[] args) {
        // 多个参数加括号
        Interface1 i1 = (i) -> i * 2;
        // 这种是最常见的写法(单个参数)
        Interface1 i2 = i -> i * 2;

        // 加类型
        Interface1 i3 = (int i) -> i * 2;
        
        // 多行
        Interface1 i4 = (int i) -> {
            System.out.println("----------");
            return i * 2;
        };
    }
}


 

第3集 jdk8新特性

3.1 函数接口

/**
 * 函数接口,要求只有一个方法,加上注解后如果有多个方法会报错
 * jdk8之后要求我们接口设计尽量的小,一个接口只做一件事,单一责任制
 * 推荐添加注解 @FunctionalInterface
 * 如果想要实现多个接口功能可以使用继承
 */
@FunctionalInterface
interface Interface1 {
    int doubleNum(int i);
}

3.2 默认方法

@FunctionalInterface
interface Interface1 {

    int doubleNum(int i);

    /**
     * jdk8对于接口新增的默认方法,可以不被实现,也可以被重写
     * 使用default关键字可以在接口中实现方法
     * 和类中的方法一样,可以在方法使用this调用接口中的方法
     * @param x
     * @param y
     * @return
     */
    default int add(int x, int y) {
        return x + y;
    }
}

public class LambdaDemo1 {

    public static void main(String[] args) {
        Interface1 i1 = (i) -> i * 2;
        // 调用接口默认方法
        System.out.println(i1.add(3, 7));
        System.out.println(i1.doubleNum(20));
    }
}

关于jdk8接口新增默认方法和static方法参考链接:

https://www.cnblogs.com/JunFengChan/p/9417004.html

3.3 关于接口多继承默认方法冲突问题

@FunctionalInterface
interface Interface1 {

    int doubleNum(int i);

    default int add(int x, int y) {
        return x + y;
    }
}

@FunctionalInterface
interface Interface2 {

    int doubleNum(int i);

    default int add(int x, int y) {
        return x + y;
    }
}


@FunctionalInterface
interface Interface3 extends Interface1, Interface2 {

    /**
     * 当接口继承的父类中的默认方法冲突时
     * 需要指定继承哪个接口的默认方法或者重写该方法
     * 如果不进行处理会报错
     * @param x
     * @param y
     * @return
     */
    @Override
    default int add(int x, int y) {
        // 继承Interface1中的默认方法
        return Interface1.super.add(x, y);
    }
}

关于jdk8默认方法冲突参考链接

https://blog.csdn.net/f641385712/article/details/94138446


 

第4集 函数接口

4.1 jdk8自带函数接口

先用我们之前的写法实现格式化数字

package lambda;

import java.text.DecimalFormat;

interface IMoneyFormat {
    String format(int i);
}

class MyMoney {

    private final int money;

    public MyMoney(int money) {
        this.money = money;
    }

    public void printMoney(IMoneyFormat moneyFormat) {
        System.out.println("我的存款:" + moneyFormat.format(this.money));
    }
}

public class MoneyDemo {

    public static void main(String[] args) {
        MyMoney me = new MyMoney(9999999);
        me.printMoney(i -> new DecimalFormat("#,###").format(i));
		
    }

}
// 输出 我的存款:9,999,999

此时我们只需要知道输入是什么,输出是什么,所以这时候我们可以不用自己定义一个函数接口,直接使用jdk8自带的函数接口 Function<T, R>

修改后代码如下,我们此时可以删除ImoneyFormat这个接口

class MyMoney {

    private final int money;

    public MyMoney(int money) {
        this.money = money;
    }

    public void printMoney(Function<Integer, String> moneyFormat) {
        System.out.println("我的存款:" + moneyFormat.apply(this.money));
    }
}

使用Fuction函数接口的好处就是我们可以定义更少的函数接口

另外Fuction函数接口还支持链式调用

public class MoneyDemo {

    public static void main(String[] args) {
        MyMoney me = new MyMoney(9999999);
        // 这里可以使用ctrl + alt + v将lambda表达式抽取出来
        Function<Integer, String> moneyFormat = i -> new DecimalFormat("#,###").format(i);
        // 函数接口链式操作
        me.printMoney(moneyFormat.andThen(s -> "人名币" + s));

    }

}

使用起来更加灵活

4.2 jdk8内置的函数接口

1、JAVA内置四大核心函数式接口

2、其它函式接口(和上面的差不多所以就不演示了)

参考链接:

https://blog.csdn.net/weixin_39020878/article/details/109529284
https://www.runoob.com/java/java8-functional-interfaces.html

代码示例

package lambda;

import java.util.function.*;

public class FunctionDemo {

    public static void main(String[] args) {

        // 四大核心核心函数式接口
        // Predicate<T>断言函数接口,参数 T,返回 boolean
        // IntPredicate 推荐优先写已经有标注类型的函数接口,这样就不需要自己指定泛型了
        Predicate<Integer> predicate = i -> i > 0;
        System.out.println(predicate.test(-9));

        // Consumer<T>消费函数接口,参数T,返回 void
        // IntConsumer
        Consumer<String> consumer = s -> System.out.println(s);
        consumer.accept("输入的数据");

        // Supplier<T>供应商函数接口,无参数,返回 T
        Supplier<String> supplier = () -> "dog";
        System.out.println(supplier.get());

        // Function<T,R>函数接口,参数T,返回R
        Function<Integer, Integer> function = i -> i * 2;
        System.out.println(function.apply(1));


    }
}


 

第5集 方法引用

使用函数接口的方式来创建和调用对象中的方法

package lambda;

import java.util.function.*;

class Dog {

    private String name = "一条狗";

    /**
     * 默认10斤狗粮
     */
    private int food = 10;

    public Dog() {

    }

    /**
     * 带参数的构造函数
     * @param name
     */
    public Dog(String name) {
        this.name = name;
    }

    /**
     * 狗叫,静态方法
     * @param name
     */
    public static void bark(String name) {
        System.out.println(name + "叫了");
    }

    /**
     * 吃狗粮
     * JDK 默认会把当前实例传入到非静态方法, 参数名为this,位置是第一个
     * @param num
     * @return 还剩下多少斤
     */
    public int eat(int num) {
        System.out.println("吃了" + num + "斤狗粮");
        this.food -= num;
        return this.food;
    }

    @Override
    public String toString() {
        return name;
    }
}

public class MethodReferenceDemo {

    public static void main(String[] args) {

        // 方法引用
        Consumer<String> consumer = System.out::println;
        consumer.accept("接受的数据");

        // 静态方法的方法引用
        Consumer<String> consumer2 = Dog::bark;
        consumer2.accept("哮天犬");

        // 非静态方法,使用对象实例的方法引用
        Dog dog = new Dog();
        // Function<Integer, Integer> function = dog::eat;
        // 因为函数参数和返回值相同,所以我们可以使用一元函数接口UnaryOperator替代Function
        // java中变量是传参而不是传引用,所以就算后面把dog变量置为空也不会影响已经使用的参数
        IntUnaryOperator function = dog::eat; 
        System.out.println("还剩下" + function.applyAsInt(2) + "斤");

        // 用Dog:eat的方式访问非静态方法(使用类名来引用非静态方法)
        BiFunction<Dog, Integer, Integer> eatFunction = Dog::eat;
        System.out.println("还剩下" + eatFunction.apply(dog, 2) + "斤");

        // 构造函数的方法引用
        Supplier<Dog> supplier = Dog::new;
        System.out.println("创建了新对象:" + supplier.get());

        // 带参数的构造函数的方法引用
        Function<String, Dog> function2 = Dog::new;
        System.out.println("创建了新对象:" + function2.apply("旺财"));

    }
}

关于idea的字节码查看插件

jclasslib bytecode viewer的使用,参考链接

https://zhuanlan.zhihu.com/p/93910641


 

第6集 类型推断

我们在使用lambda表达式时需要使用以下几种方式来指定它所返回的接口类型

package lambda;

@FunctionalInterface
interface Imath {
    int add(int x, int y);
}

@FunctionalInterface
interface Imath2 {
    int sub(int x, int y);
}

public class TypeDemo {

    public static void main(String[] args) {
        
        // 变量类型定义
        Imath lambda = (x, y) -> x + y;
        
        // 数组里
        Imath[] lambdas = {(x, y) -> x + y};

        // 强转
        Object lambda2 = (Imath)(x, y) -> x + y;

        // 通过返回类型
        Imath create = createLambda();

        TypeDemo demo = new TypeDemo();
        // 当只有一个test方法时lambda表达式会根据需要的参数类型来判断接口类型
        // demo.test((x, y) -> x + y);
        // 当接口重载有二义性时我们需要强转来指定lambda表达式返回的类型
        demo.test((Imath)(x, y) -> x + y);
    }

    public void test(Imath math) {
        System.out.println(math.add(1, 2));
    }

    public void test(Imath2 math) {
        System.out.println(math.sub(1, 2));
    }


    private static Imath createLambda() {
        return (x, y) -> x + y;
    }
}


 

第7集 变量引用

package lambda;

import java.util.function.Consumer;

/**
 * 变量引用
 */
public class VarDemo {

    public static void main(String[] args) {
        String str = "我们的时间";
        // 内部类引用外部变量必须是final类型,否则会编译出错
        // jdk8以后我们可以不用写,但实际上我们还是不能修改(本质上还是final,只是让我们能偷点懒而已)
        // str = "dog";
        // java中参数传递实际上是传值而不是传变量(引用)
        // 也就是直接传递的对象所在的地址,但是外面的str和里面的str是两个不同的变量(两个不同的引用)
        // 如果外面str一开始创建时是指向"我们的时间",而后来又指向了"dog"
        // 就有可能导致里面的str指向的还是"我们的时间",但是外面的str已经改变了
        // 这样就可能会产生一些错误的结果,产生二义性
        Consumer<String> consumer = s -> System.out.println(s + str);
        consumer.accept("999 ");

    }
}

另外一种解释

https://blog.csdn.net/u011617742/article/details/51613519


 

第8集 级联表达式和柯里化

package lambda;

import java.util.function.Function;

/**
 * 级联表达式和柯里化
 * 柯里化:把多个参数的函数转换为只有一个参数的函数
 * 柯里化的目的:函数标准化
 * 高阶函数:返回函数的函数
 */
public class CurryDemo {

    public static void main(String[] args) {
        // 级联表达式,它的含义是它是一个函数接口
        // 输入一个Integer类型的值返回一个函数接口,实现了x + y
        Function<Integer, Function<Integer, Integer>> fun = x -> y -> x + y;
        System.out.println(fun.apply(2).apply(3));

        Function<Integer, Function<Integer, Function<Integer, Integer>>> fun2 = x -> y -> z -> x + y + z;
        System.out.println(fun2.apply(2).apply(3).apply(4));

        int[] nums = {2, 3, 4};
        Function f = fun2;
        for (int num : nums) {
            if (f instanceof Function) {
                Object obj = f.apply(num);
                if (obj instanceof Function) {
                    f = (Function) obj;
                } else {
                    System.out.println("调用结束:结果为:" + obj);
                }
            }
        }
    }
}

虽然是学了这么个东西,但是我实在不明白这玩意儿有啥作用,然后我就去查柯里化的应用场景,结果碰巧找到了视频里老师写的知乎文章,然后仔细一看他也说没找到有啥使用场景,所以了解有那么个用法就行了,至于使用之类的,以后再说

https://zhuanlan.zhihu.com/p/35841424



 

第二章 Stream流编程

第1集 Stream流编程概念

什么是 Stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

参考链接:

https://www.runoob.com/java/java8-streams.html

# 另外比较详细的博客
https://blog.csdn.net/ZytheMoon/article/details/86544013

代码示例

package stream;

import java.util.stream.IntStream;

public class StreamDemo1 {

    public static void main(String[] args) {
        int[] nums = {1, 2, 3};
        // 外部迭代
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        System.out.println("结果为:" + sum);

        // 使用stream的内部迭代
        // map就是中间操作(懒加载,返回stream的操作)
        // sum就是终止操作(副作用)
        // int sum2 = IntStream.of(nums).map(i -> i * 2).sum();
        int sum2 = IntStream.of(nums).map(StreamDemo1::doubleNum).sum();
        System.out.println("结果为:" + sum2);

        System.out.println("惰性求值就是终止没有调用的情况下,中间操作不会执行");
        IntStream.of(nums).map(StreamDemo1::doubleNum);

    }

    public static int doubleNum(int i) {
        System.out.println("执行了乘以2");
        return i * 2;
    }


}


 

第2集 流的创建

代码示例

package stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamDemo2 {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        // 从集合创建
        list.stream();
        list.parallelStream();

        // 从数组创建
        Arrays.stream(new int[]{2, 3, 5});

        // 创建数字流
        IntStream.of(1, 2, 3);
        // rangeClosed包含结束节点,range不包含
        IntStream.rangeClosed(1, 10);

        // 使用random创建一个无限流(非静态方法)
        // 因为它是一个无限流,所以需要执行一些短路操作,例如limit,否则它会一直执行下去
        new Random().ints().limit(10);

        // 自己产生流
        Random random = new Random();
        Stream.generate(random::nextInt).limit(20);
    }
}


 

第3集 流的中间操作

无状态和有状态操作的解释:

  • 诸如map或者filter会从输入流中获取每一个元素,并且在输出流中得到一个结果,这些操作没有内部状态,称为无状态操作。
  • 但是像reduce、sum、max这些操作都需要内部状态来累计计算结果,所以称为有状态操作。

参考链接:

https://blog.csdn.net/qq_27037443/article/details/103680120

关于map和flatmap

  • map只是一维 1对1 的映射

  • 而flatmap可以将一个2维的集合映射成一个一维,相当于他映射的深度比map深了一层

参考链接:

https://www.cnblogs.com/wangjing666/p/9999666.html

代码示例

package stream;

import java.util.Random;
import java.util.stream.Stream;

public class StreamDemo3 {

    public static void main(String[] args) {

        String str = "my name is 007";
        // 把每个单词的长度大于2的长度打印出来
        // filter传入一个Predicate接口函数,map传入一个Function函数接口
        System.out.println("------filter and map------");
        Stream.of(str.split(" ")).filter(s -> s.length() > 2).map(String::length).forEach(System.out::println);

        // flatMap A -> B属性(是个集合),最终得到所有的A元素里面的所有B属性集合
        // intStream/longStream并不是Stream的子类所以经常要进行装箱 boxed
        // 获取字符串中每个单词的字符
        System.out.println("------flatMap--------");
        Stream.of(str.split(" ")).flatMap(s -> s.chars().boxed()).forEach(i -> System.out.println((char)i.intValue()));

        // peek 用于debug,是个中间层操作,和forEach类似,forEach是终止操作
        System.out.println("-------peek---------");
        Stream.of(str.split(" ")).peek(System.out::print).forEach(System.out::println);

        // limit使用,主要用于无限流
        new Random().ints().filter(i -> i > 100 && i < 10000).limit(10).forEach(System.out::println);
    }
}

补充

这里我们发现在流操作中无法使用sout快捷键,所以我们这里可以自定义快捷键,例如:

s::n + Tab 输出 System.out::println

详细设置方法参考:

https://blog.csdn.net/x_iya/article/details/73163631


 

第4集 流的终止操作

短路操作就是不需要等待所有结果都计算完就可以结束流的操作,只需要得到指定个结果后就可以结束的终止操作

推荐一篇非常优秀的文章

# 关于stream api和原始集合遍历方式的对比(推荐)
https://www.cnblogs.com/wmyskxz/p/11296063.html

代码示例:

package stream;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamDemo4 {

    public static void main(String[] args) {
        String str = "my name is 007";

        // 迭代器
        // 使用并行流,乱序(效率更高)
        str.chars().parallel().forEach(i -> System.out.print((char)i));
        System.out.println();
        // 使用 forEachOrdered保证顺序
        str.chars().parallel().forEachOrdered(i -> System.out.print((char)i));
        System.out.println();

        // 收集器
        // 收集到list
        List<String> list = Stream.of(str.split(" ")).collect(Collectors.toList());
        System.out.println(list);
        // 收集到Set
        Set<String> set = Stream.of(str.split(" ")).collect(Collectors.toSet());
        System.out.println(set);

        // 归约,将流中的元素结合起来得到一个值
        // 使用reduce 拼接字符串
        Optional<String> letters = Stream.of(str.split(" ")).reduce((s1, s2) -> s1 + "|" + s2);
        // 使用optional进行空判断
        System.out.println(letters.orElse(null));

        // 带初始化值的reduce,但是默认值也会被算入元素之中
        String reduce = Stream.of(str.split(" ")).reduce("", (s1, s2) -> s1 + "|" + s2);
        System.out.println(reduce);

        // 计算所有单词总长度
        Integer length = Stream.of(str.split(" ")).map(String::length).reduce(0, (s1, s2) -> s1 + s2);
        System.out.println(length);

        // max的使用,找出最长的单词
        Optional<String> max = Stream.of(str.split(" ")).max(Comparator.comparingInt(String::length));
        System.out.println(max.orElse(null));
        // min的使用,找出最短的单词
        Optional<String> min = Stream.of(str.split(" ")).min(Comparator.comparingInt(String::length));
        System.out.println(min.orElse(null));
        // count的使用,统计有多少个单词
        long count = Stream.of(str.split(" ")).count();
        System.out.println(count);

        // 使用 allMatch判断是否所有单词长度都大于1,返回boolean
        // anyMatch和noneMatch用法差不多,都是返回boolean
        boolean all = Stream.of(str.split(" ")).anyMatch(s -> s.length() > 1);
        System.out.println(all);

        // 使用 findFirst 短路操作
        OptionalInt findFirst = new Random().ints().findFirst();
        System.out.println(findFirst.orElse(0));
        // 使用 findAny 短路操作
        OptionalInt findAny = new Random().ints().findAny();
        System.out.println(findAny.orElse(0));



    }
}


 

第5集 并行流

package stream;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class StreamDemo5 {

    public static void main(String[] args) {

        // peek在jdk10以后声明了使用count终止操作时不会执行peek中的方法,所以我们换成了sum()
        // 调用parallel产生一个并行流
        // IntStream.range(1, 100).parallel().peek(StreamDemo5::debug).sum();

        // 现在要实现这样一个效果:先并行,再串行
        // 多次调用 parallel / sequential,以最后一次调用为准
//        IntStream.range(1, 100)
//                // 调用parallel产生并行流
//                .parallel().peek(StreamDemo5::debug)
//                // 调用sequential产生串行流
//                .sequential().peek(StreamDemo5::debug2)
//                .sum();

        // 并行流使用的线程池:ForkJoinPool.commonPool
        // 默认的线程数时当前机器的cpu个数
        // 使用这个属性可以修改默认的线程数
//        System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20");
//        IntStream.range(1, 100).parallel().peek(StreamDemo5::debug).sum();

        // 使用自己的线程池,不适用默认线程池,防止任务被阻塞
        ForkJoinPool pool = new ForkJoinPool(20);
        pool.submit(() -> IntStream.range(1, 100).parallel().peek(StreamDemo5::debug).sum());
        pool.shutdown();

        // 主线程停止的话线程池也会被结束,所以我们在这里让它等待一下
        synchronized (pool) {
            try {
                pool.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static void debug(int i) {
        System.out.println(Thread.currentThread().getName() + "debug " + i);
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void debug2(int i) {
        System.err.println("debug " + i);
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


 

第6集 收集器

package stream;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 学生 对象
 */
class Student {
    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private int age;

    /**
     * 性别
     */
    private Gender gender;

    /**
     * 班级
     */
    private Grade grade;

    public Student(String name, int age, Gender gender, Grade grade) {
        super();
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.grade = grade;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Grade getGrade() {
        return grade;
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }

    public Gender getGender() {
        return gender;
    }

    public void setGender(Gender gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "[name=" + name + ", age=" + age + ", gender=" + gender
                + ", grade=" + grade + "]";
    }

}

/**
 * 性别
 */
enum Gender {
    MALE, FEMALE
}

/**
 * 班级
 */
enum Grade {
    ONE, TWO, THREE, FOUR
}

public class CollectDemo {

    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
                new Student("小明", 10, Gender.MALE, Grade.ONE),
                new Student("大明", 9, Gender.MALE, Grade.THREE),
                new Student("小白", 8, Gender.FEMALE, Grade.TWO),
                new Student("小黑", 13, Gender.FEMALE, Grade.FOUR),
                new Student("小红", 7, Gender.FEMALE, Grade.THREE),
                new Student("小黄", 13, Gender.MALE, Grade.ONE),
                new Student("小青", 13, Gender.FEMALE, Grade.THREE),
                new Student("小紫", 9, Gender.FEMALE, Grade.TWO),
                new Student("小王", 6, Gender.MALE, Grade.ONE),
                new Student("小李", 6, Gender.MALE, Grade.ONE),
                new Student("小马", 14, Gender.FEMALE, Grade.FOUR),
                new Student("小刘", 13, Gender.MALE, Grade.FOUR));

        // 得到所有学生的年龄列表
        // s -> s.getAge() --> Student::getAge,不会多生成一个类似 lambda$0这样的函数
        // 这里的Student::getAge是Function<Student, Integer> fun = s -> s.getAge()的简写
        // Student::getAge这种写法方法需要用静态修饰,没有参数的话似乎也可以
        List<Integer> ages = students.stream().map(Student::getAge)
                .collect(Collectors.toList());
        System.out.println("所有学生的年龄:" + ages);

//        // 达到去重效果
//        Set<Integer> set = students.stream().map(Student::getAge)
//                .collect(Collectors.toSet());
//        System.out.println("所有学生的年龄:" + set);
        // 使用toCollection可以自己指定集合实现类
        Set<Integer> set = students.stream().map(Student::getAge)
                .collect(Collectors.toCollection(TreeSet::new));
        System.out.println("所有学生的年龄:" + set);

        // 统计汇总信息
        IntSummaryStatistics summaryStatistics = students.stream()
                .collect(Collectors.summarizingInt(Student::getAge));
        System.out.println("年龄汇总信息:" + summaryStatistics);

        // 分块
        Map<Boolean, List<Student>> genders = students.stream()
                .collect(Collectors.partitioningBy(s -> s.getGender() == Gender.MALE));
        System.out.println("男女学生列表:" + genders);

        // 分组
        Map<Grade, List<Student>> grades = students.stream()
                .collect(Collectors.groupingBy(Student::getGrade));
        System.out.println("学生班级列表:" + grades);

        // 得到所有班级学生的个数
        Map<Grade, Long> gradesCount = students.stream()
                .collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));
        System.out.println("学生班级个数列表:" + gradesCount);
    }
}

收集器参考链接:

https://blog.csdn.net/qq_38361800/article/details/106895325


 

第7集 Stream运行机制

package stream;

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;


/**
 * 验证steam运行机制
 * 1. 所有操作时链式调用,一个元素只迭代一次
 * 2. 每一个中间操作返回一个新的流,流里面有一个属性sourceStage指向同一个地方,就是Head
 * 3. Head -> nextStage -> nextStage-> ... -> null
 * 4. 有状态操作会把无状态操作截断,单独处理
 * 5. 并行环境下,有状态的中间操作不一定能并行操作
 * 6. parallel/sequential 这两个操作也是中间操作(也是返回stream)
 *      但是他们不创建流,他们只修改Head的并行标志
 */
public class RunStream {

    public static void main(String[] args) {
        Random random = new Random();
        // 随机产生数据
        Stream<Integer> stream = Stream.generate(random::nextInt)
                // 产生500个(无限流需要短路操作)
                .limit(500)
                // 第一个无状态操作
                .peek(s -> print("peek: " + s))

                // 有状态操作
                .sorted((i1, i2) -> {
                    print("排序:" + i1 + ", " + i2);
                    return i1.compareTo(i2);
                })

                // 第二个无状态操作
                .filter(s -> {
                    print("filter" + s);
                    return s > 1000000;
                }).parallel();

         // 终止操作
        stream.allMatch(s -> true);
    }

    /**
     * 打印日志并sleep5毫秒
     * @param s
     */
    public static void print(String s) {
        // 带线程名(测试并行情况)
        System.out.println(Thread.currentThread().getName() + ">" + s);
        try {
            TimeUnit.MILLISECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

原文地址 

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值