Java响应式编程

一、lambda表达式

1.1 lambda简介

Lambda是Java 8中添加的新特性,Lambda表达式本质上是匿名方法,其底层还是通过invokedynamic指令来生成匿名类来实现。它提供了更为简单的语法和写作方式,允许你通过表达式来代替函数式接口。

1.2 lambda语法

语法:

​ (parameters) -> expression 或 (parameters) ->{ statements; }

特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值

注意:需要注意 lambda 表达式隐含了 return 关键字,所以在单个的表达式中,我们无需显式的写 return 关键字,但是当表达式是一个语句集合的时候则需要显式添加 return 关键字,并用花括号{ } 将多个表达式包围起来。

示例:

public class Lambda {
    public static void main(String[] args) {

        //1.不需要参数
        Example1 example1 = () -> 5;

        //2.接收一个参数。有一个返回值   一个参数的时候 () 可以省略
        Example2 example2 = a -> 2 * a;
        Example2 example21 = (a) -> 2 * a;

        //3.接收2个参数。返回差值
        Example3 example3 = (a, b) -> a - b;

        //4.接收2个参数,打印值,没有返回值
        Example4 example4 = (a, b) -> System.out.println("我是字符串" + a + "与数值:" + b);
        //当表达式是一个语句集合的时候则需要显式添加 return 关键字,并用花括号{ } 将多个表达式包起来
        Example4 example41 = (a, b) -> {
            if (b > 10) {
                System.out.println(a);
            }
        };
    }

    interface Example1 {
        int test();
    }

    interface Example2 {
        int test(int a);
    }

    interface Example3 {
        int test(int a, int b);
    }

    interface Example4 {
        void test(String b, int a);
    }
}

通过上面的接口我们可以发现:我们定义的接口都只有一个抽象方法,也称为:函数式接口

lambda 表达式的使用需要借助于函数式接口,也就是说只有函数式接口出现地方,我们才可以将其用 lambda 表达式进行简化。

函数接口的定义:仅含有一个抽象方法的接口。

注意:如果接口存在多个方法,我们可以使用default关键字修饰方法,定义方法的方法体,保证接口只存在一个抽象方法,那么这个接口也符合函数式接口的定义。

1.3 @FunctionalInterface

用于标记该接口是一个函数式接口,不过该注解是可选的,当添加了该注解之后,编译器会限制了该接口只允许有一个抽象方法,否则报错,所以推荐为函数式接口添加该注解。

jdk1.8自带的函数接口:

接口输入参数返回类型说明
PredicateTboolean断言
ConsumerT/消费一个数据
Function<T, R>TR输入T,输出R的函数
Supplier/T提供一个数据
UnaryOperatorTT一元函数(输出输入类型相同)
BiFunction<T, U, R>(T, U)R2个输入的函数
BinaryOperator(T, T)T二元函数(输出输入类型相同)

事例

public static void main(String[] args) {
  //断言函数
  Predicate<Integer> predicate = i -> i > 0;
  System.out.println(predicate.test(-10));

  //消费函数接口
  Consumer<String> consumer = System.out::println;
  consumer.accept("测试消费函数接口");

  //输入输出函数接口
  Function<String, Integer> function = String::length;
  System.out.println(function.apply("apply"));

  //提供数据函数接口
  Supplier<String> supplier = () -> "hello world";
  System.out.println(supplier.get());

  //一元函数接口
  UnaryOperator<String> unaryOperator = t -> "Hello," + t;
  System.out.println(unaryOperator.apply("张三"));

  //两个输入函数接口
  BiFunction<String, Integer, Map<String, Integer>> biFunction = (k, v) -> {
    Map<String, Integer> map = new HashMap<>();
    map.put(k, v);
    return map;
  };
  System.out.println(biFunction.apply("name", 9999).get("name"));

  //二元函数接口
  BinaryOperator<String> binaryOperator = (t1, t2) -> t1 + ":" + t2;
  System.out.println(binaryOperator.apply("张三", "晚上吃什么?"));
}

1.4 方法引用

  • 静态方法引用
  • 非静态方法引用
  • 构造器引用
public class LambdaDemo {

    public static void main(String[] args) {
        //参数声明
        say((n, m) -> n + m);
        //单行表达式
        say((String n, String m) -> n + m);
        //语句块
        say((String n, String m) -> {
            return n + m;
        });
        //1.静态方法引用,引起方法【sayOne】的参数与函数式接口【LambdaInteracce】的参数类型一致,且【sayOne】为静态方法
        say(LambdaDemo::sayOne);

        //2.非静态方法引用
        //实例对象引用
        say(new LambdaDemo()::sayTwo);
        //参数对象引用,这个相当于say((n, m) -> n.concat(m));
        say(String::concat);

        //3.构造函数引用
        //无参构造
        Supplier<LambdaDemo> lb1 = LambdaDemo::new;
        System.out.println(lb1.get());
        //有参构造
        Function<String, LambdaDemo> lb2 = LambdaDemo::new;
        System.out.println(lb2.apply("张三"));
    }

    private String name;

    public LambdaDemo() {

    }

    public LambdaDemo(String name) {
        this.name = name;
    }

    /**
     * 什么函数表达式
     */
    @FunctionalInterface
    interface LambdaInteracce {
        String sayHello(String name, String message);
    }

    private static void say(LambdaInteracce lambda) {
        String result = lambda.sayHello("张三", "Hello World");
        System.out.println(result);
    }

    private static String sayOne(String name, String message) {
        return name + message;
    }

    private String sayTwo(String name, String message) {
        return name + message;
    }

    @Override
    public String toString() {
        return "创建新对象:LambdaDemo{} name: " + this.name;
    }
}

1.5 类型推断

public class Test03 {

    public static void main(String[] args) {
        //变量类型定义
        IMath math1 = (x, y) -> x + y;

        //数组定义
        IMath[] maths = {(x, y) -> x + y};

        //强制转换
        Object math2 = (IMath) (x, y) -> x + y;

        //通过返回类型
        IMath math3 = getMath();
    }

    private static IMath getMath(){
        return (x, y) -> x + y;
    }

    interface IMath {
        int add(int x, int y);
    }
}

1.6 变量引用

jdk1.8之前,内部类如果需要访问外部变量,那这个变量必须声明为final类型,在jdk1.8中也是一致的,只不过jdk1.8中可以不加final这个关键字,但是实际上这个变量也是不能修改的。

为什么内部类访问外部变量必须是final修饰的? – 因为Java中传参的形式是传值,而不是传引用。

1.7 级联表达式和柯里化

级联表达式:有多个->的表达式。

Function<Integer, Function<Integer, Integer>> f = x -> y -> x + y;
System.out.println(f.apply(100).apply(200));

柯里化:把多个参数的函数转换为只有一个参数的函数。

柯里化目的:函数标准化。

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

二、Stream流编程

2.1 概念

​ 首先要了解的是java8中的stream与InputStream和OutputStream是完全不同的概念,,stream是用于对集合迭代器的
增强,使之完成能够完成更高效的聚合操作(过滤排序、统计分组)或者大批量数据操作。此外与stream与lambada表达示结合后编码效率与大大提高,并且可读性更强。

示例:

//获取所有红色苹果的总重量
appleStore.stream()
  .filter(a -> "red".equals(a.getColor()))
  .mapToInt(w -> w.getWeight()).sum();
//基于颜色计算平均重量
Map<String, Double> collect = appleList.stream()
  .collect(Collectors.groupingBy(a -> a.getColor(), Collectors.averagingDouble(a -> a.getWeight())));

2.2 外部迭代和内部迭代

public static void main(String[] args) {
  int[] nums = {1, 2, 3};
  //外部迭代
  int sum = 0;
  for (int i = 0; i < nums.length; i++) {
    sum += nums[i];
  }
  System.out.println(sum);

  //内部迭代
  int sum1 = IntStream.of(nums).sum();
  System.out.println(sum1);
}

2.3 流的创建

相关方法
集合Collection.stream/parallelStream
数组Arrays.stream
数字IntStream/LongStream.range/rangeClosed
Random.ints/longs/doubles
自己创建Stream.generate/iterate

示例

基于Connection:

//示例
new ArrayList<>().stream().forEach(System.out::println);

基于Arrays:

//示例
Arrays.stream(new String[]{"a", "b", "c"}).forEach(System.out::println);

基于Stream API生成:

//示例
Stream.of("a", "b", "c").forEach(System.out::println)
Stream.of(new String[]{"a","b","c"}).forEach(System.out::println);
Stream.iterate(0, a -> a+1).limit(10).forEach(System.out::println);
Random random = new Random();
Stream.generate(() -> random.nextInt()).limit(10).forEach(System.out::println);
//int数字流
IntStream.of(1, 2, 3, 4).forEach(System.out::println);
//long数字流
LongStream.of(1, 2, 3, 4).forEach(System.out::println);
//以增量步长1从startInclusive(包括)到endExclusive(不包括)返回顺序的有序LongStream。
LongStream.range(100, 200).limit(10).forEach(s -> System.out.println(s));
//创建10个1000-2000的数字
IntStream.rangeClosed(1000, 2000).limit(10).forEach(System.out::println);
//随机数流,随机生成100-200的10个数字
new Random().ints(100, 200).limit(10).forEach(System.out::println);

IntStream.range()和IntStream.rangeClosed区别:其实区别就是开区间和闭区间的区别,如:[1,20)和[1,20]的区别

IntStream.range(1,20).forEach(i-> System.out.print(i));//返回一个1-19的数字流
IntStream.rangeClosed(1,20).forEach(i-> System.out.print(i));//返回的一个1-20的数字流

2.3 中间操作/终值操作和惰性求值

2.3.1 中间操作

中间操作:返回stream的操作。

有状态和无状态的区分:函数接口是1个参数的就是无状态操作;有2个参数的就是有状态操作。

操作类型相关方法
无状态操作map/mapToXxx
flatMap/flatMapToXxx
filter
peek
unordered
有状态操作distinct
sorted
limit/skip
  • map/mapToXxx:将A对象转换成B对象。
  • flatMap/flatMapToXxx:A->B属性(集合),最终得到所有的A元素里面的所有B属性集合。
  • peek用于debug,是个中间操作,与forEach的区别:forEach是终值操作。
  • unordered:一般在并行流中才会使用,平时用的也不多。
  • limit:主要用于无限流,用来做限制操作,防止无限循环。
  • skip:用于跳过一些数据。

示例:

filter、map:

@Test
public void test2(){
  String str = "my name is zhangsan";
  Arrays.stream(str.split(" ")) //使用Arrays创建流
    .filter(s -> s.length() > 2) //过滤长度小于2的数据
    .map(s -> s.length()) //将String数据转成int
    .forEach(System.out::println);//遍历打印数据
}

flatMap:

@Test
public void test3(){
  String str = "my name is zhangsan";
  Arrays.stream(str.split(" "))
    .flatMap(s -> s.chars().boxed())
    .forEach(i -> System.out.println((char)i.intValue()));
}

peek:

@Test
public void test4(){
  String str = "my name is zhangsan";
  Arrays.stream(str.split(" "))
    .peek(System.out::println)
    .forEach(System.out::println);
}

注意:intStream/longStream并不是Stream的子类,所以需要进行装箱,调用boxed()。

惰性求值:只刻画了stream,但是并没有产生新的集合,而像这种没有实际功能,只是描述stream的操作,就叫做惰性求值。

2.3.2 终值操作

操作类型相关方法
非短路操作forEach/forEachOrdered
collect/toArray
reduce
min/max/count
短路操作findFirst/findAny
allMatch/anyMatch/noneMatch
  • forEachOrdered:主要用于并行流,用来保证顺序。
  • collect/toArray:主要用于数据收集操作,可以转成数组或List。
  • reduce:累加器。

示例:

forEach/forEachOrdered:

@Test
public void test5(){
  String str = "my name is zhangsan";
  //使用并行流
  Arrays.stream(str.split(" ")).parallel().forEach(System.out::print);
  //并行流,使用forEachOrdered保证顺序
  Arrays.stream(str.split(" ")).parallel().forEachOrdered(System.out::print);
}

collect/toArray:

@Test
public void test6(){
  String str = "my name is zhangsan";
  //收集到List
  List<String> list = Arrays.stream(str.split(" ")).collect(Collectors.toList());
  System.out.println(list);//[my, name, is, zhangsan]
  Object[] array = Arrays.stream(str.split(" ")).toArray();
  System.out.println(Arrays.toString(array));//[my, name, is, zhangsan]
}

reduce:

@Test
public void test7() {
  String str = "my name is zhangsan";
  //使用reduce拼接字符串
  Optional<String> reduce = Arrays.stream(str.split(" ")).reduce((t1, t2) -> t1 + "|" + t2);
  String result = reduce.orElse("");
  System.out.println(result);//my|name|is|zhangsan

  //带初始值的reduce
  String reduce1 = Arrays.stream(str.split(" ")).reduce("", (t1, t2) -> t1 + "-" + t2);
  System.out.println(reduce1);

  //计算单词总长度
  Integer reduce2 = Arrays.stream(str.split(" ")).map(s -> s.length()).reduce(0, (t1, t2) -> t1 + t2);
  System.out.println(reduce2);
}

min/max/count:

@Test
public void test8(){
  String str = "my name is zhangsan";
  //获取长度最大的单词
  Optional<String> max = Stream.of(str.split(" ")).max((s1, s2) -> s1.length() - s2.length());
  System.out.println(max.get());

  //获取长度最小的单词
  Optional<String> min = Stream.of(str.split(" ")).min((s1, s2) -> s1.length() - s2.length());
  System.out.println(min.get());

  //获取单词总数
  long count = Stream.of(str.split(" ")).count();
  System.out.println(count);
}

2.4 并行流

// 并行流创建
new ArrayList<>().parallelStream().forEach(System.out::println);
LongStream.rangeClosed(100,200)..parallel().forEach(System.out::println);
@Test
public void test11(){
  //注意:多次调用parallel/sequential,以最后一次为主。
  LongStream.rangeClosed(100,200)
  	.parallel().peek(s-> System.out.println(s)) //并行流
  	.sequential().peek(s -> System.out.println(s)) //串行流
  	.count();
}

注意:多次调用parallel/sequential,以最后一次为主。

使用默认线程池:

@Test
public void test11(){
  //并行流使用的线程池:ForkJoinPool.commonPool
  //默认的线程数是当前机器cpu的个数
  //修改默认线程数:
  System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "10");
  LongStream.rangeClosed(100,200)
  	.parallel()
  	.forEach(s -> System.out.println(Thread.currentThread().getName()));
}

自定义线程池:

@Test
public void test11(){
  //使用自己的线程池,不使用默认线程池,防止任务阻塞。
  //线程名字:ForkJoinPool-1
  ForkJoinPool pool = new ForkJoinPool(10);
  pool.submit(() -> {
    LongStream.rangeClosed(100,200)
      .parallel()
      .forEach(s -> System.out.println(Thread.currentThread().getName()));
  });
  pool.shutdown();

  synchronized (pool) {
    try {
      pool.wait();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

2.5 收集器

public class Test02 {

    private static List<Student> studentList = new ArrayList<>();

    static {
        studentList.add(new Student("张三", 18, Gender.MALE, "1班"));
        studentList.add(new Student("李四", 20, Gender.MALE, "2班"));
        studentList.add(new Student("小红", 15, Gender.FEMALE, "3班"));
        studentList.add(new Student("小美", 15, Gender.FEMALE, "1班"));
        studentList.add(new Student("赵六", 22, Gender.MALE, "2班"));
        studentList.add(new Student("小颖", 20, Gender.FEMALE, "3班"));
        studentList.add(new Student("小雪", 21, Gender.FEMALE, "1班"));
        studentList.add(new Student("小彤", 23, Gender.FEMALE, "1班"));
        studentList.add(new Student("王五", 30, Gender.MALE, "3班"));
    }

    @Test
    public void test1(){
       //得到所有学生的年龄列表
       //尽量使用方法引用代替lambda表达式:s -> s.getAge() ==> Student::getAge,不会多生成一个类似lambda$0这样的函数
        List<Integer> ageList = studentList.stream().map(Student::getAge).collect(Collectors.toList());
        System.out.println(Arrays.toString(ageList.toArray()));//[18, 20, 15, 15, 22, 20, 21, 23, 30]

        //统计汇总信息
        IntSummaryStatistics collect = studentList.stream()
                .collect(Collectors.summarizingInt(Student::getAge));
        System.out.println("年龄汇总信息" + collect);//IntSummaryStatistics{count=9, sum=184, min=15, average=20.444444, max=30}

        //分块
        Map<Boolean, List<Student>> genderMap = studentList.stream()
                .collect(Collectors.partitioningBy(s -> s.getGender().equals(Gender.MALE)));
        System.out.println("性别分类:" + genderMap);

        //分组
        Map<String, List<Student>> clazzMap = studentList.stream()
                .collect(Collectors.groupingBy(Student::getClazz));
        System.out.println("班级分组:" + clazzMap);

        //得到每个班级学生个数
        Map<String, Long> stuCountMap = studentList.stream()
                .collect(Collectors.groupingBy(Student::getClazz, Collectors.counting()));
        System.out.println("班级学生个数列表" + stuCountMap);
    }
}

2.6 Stream的运行机制

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

三、Spring WebFlux开发

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值