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自带的函数接口:
接口 | 输入参数 | 返回类型 | 说明 |
---|---|---|---|
Predicate | T | boolean | 断言 |
Consumer | T | / | 消费一个数据 |
Function<T, R> | T | R | 输入T,输出R的函数 |
Supplier | / | T | 提供一个数据 |
UnaryOperator | T | T | 一元函数(输出输入类型相同) |
BiFunction<T, U, R> | (T, U) | R | 2个输入的函数 |
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的运行机制
- 所有操作都是链式调用,一个元素只迭代一次。
- 每一个中间操作都会返回一个新的流,流里面有一个属性sourceStage指向同一个地方,就是Head。
- Head -> nextStage -> nextStage -> … ->null
- 有状态操作会把无状态操作截断,单独处理。
- 并行环境下,有状态的中间操作不一定能并行操作。
- parallel/sequential也是中间操作(也是返回stream),但是他们不创建流,他们只修改Head的并行标志。