响应式编程一之基础夯实(初学必看!)
- 函数式编程
- 常见lambda表达式
- 求一个数组里面的最小值
- 代码简洁的函数式编程
- 返回指定对象的接口实例
- JDK8 新特性
- jdk8函数式接口
- predicate 判断hashmap是否为空
- consumer
- 总结
- 方法引用示例
- lambda表达式的类型推断
- 变量引用
- Stream流
- 内部迭代和外部迭代
- 中间操作和终止操作
- Stream流编程-创建流的方式
- Stream流编程-中间操作
- Stream流编程-终止操作
- 并行流
- 收集器
函数式编程
1、可以不用关注其细节【怎么做】,只需要关注命令即可【做什么】
2、可以使代码更加简洁
常见lambda表达式
求一个数组里面的最小值
public class MinDemo {
public static void main(String[] args) {
int[] nums = {12,23,34,78,67,24};
int min = Integer.MAX_VALUE;
for(int i : nums){
if(i < min){
min = i;
}
}
System.out.println(min);
// jdk8
//int的stream流然后在min方法
int min2 = IntStream.of(nums).min().getAsInt();
System.out.println(min2);
}
}
假设数组数据有几亿条数据,那么我们不用函数式编程的思想可能考虑的优化做法为使用多线程,创建一个线程池,然后将数组里面的数据进行拆分,然后采用快排的分治的思想进行解决。【比较麻烦】
而函数式编程则可以使用
int min2 = IntStream.of(nums).parallel().min().getAsInt();
代码简洁的函数式编程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("命令式编程");
}
}).start();
//函数式编程方式
new Thread(()-> System.out.println("函数式编程"));
返回指定对象的接口实例
public static void main(String[] args) {
Object target1 = new Runnable() {
@Override
public void run() {
System.out.println("命令式编程");
}
};
new Thread((Runnable) target1).start();
//jdk8
Object target2 = (Runnable)()-> System.out.println("函数式编程");
Runnable target3 = ()-> System.out.println("函数式编程");
System.out.println(target2 == target3);
new Thread((Runnable) target2).start();
}
interface Interface1{
int doubleNum(int i);
default int add(int x,int y){
return x + y;
}
}
public class LambdaDemo1 {
public static void main(String[] args) {
Interface1 i1 = (i) -> i*2;
i1.add(3,7);
i1.doubleNum(12);
//这是最常用的一种方式
Interface1 i2 = i -> i *2;
Interface1 i3 =(int i) -> i *2;
Interface1 i4 = (int i) ->{
System.out.println("lambda 表达式使用");
return i *2;
};
}
}
JDK8 新特性
1)函数式接口:接口只有一个需要实现的方法(@FunctionalInterface)
可以通过函数式接口将接口尽可能的封装的更细致一些,符合设计模式中的但一职责规范,然后接口与接口之间进行多重继承,抽取一个类把接口都给继承一遍
2)默认方法 通过default修饰
@FunctionalInterface
interface Interface1 {
int doubleNum(int i);
default int add(int x, int y) {
return x + y;
}
}
public class LambdaDemo1 {
public static void main(String[] args) {
Interface1 i1 = (i) -> i * 2;
i1.add(3, 7);
i1.doubleNum(12);
//这是最常用的一种方式
Interface1 i2 = i -> i * 2;
Interface1 i3 = (int i) -> i * 2;
Interface1 i4 = (int i) -> {
System.out.println("lambda 表达式使用");
return i * 2;
};
}
}
jdk8函数式接口
通过如下代码将money格式化
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(999999999);
me.printMoney(i ->new DecimalFormat(",###").format(i));
}
}
第二种写法用Function
interface IMoneyFormat{
String format(int i );
}
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));
}
}
public class MoneyDemo {
public static void main(String[] args) {
MyMoney me = new MyMoney(9999999);
me.printMoney(i -> new DecimalFormat(",###").format(i));
// 我们学习lambda表达式就可以这样写
// Function<Integer,String > moneyFormate=i -> new DecimalFormat("#.###").format(i);
// 函数接口链式操作
// me.printMoney(moneyFormate.andThen(s->"人民币" +s));
}
}
predicate 判断hashmap是否为空
public static boolean isNullOrEmpty(Map map) {
//这里需要指定范型,这里是Map,指定好了范型后,方法里面才能用Map对象的方法
Predicate<Map> predicate = (map1)->{
if (null == map1 || 0 == map1.size()) {
System.out.println("map 的 size is 0");
return true;
} else {
return false;
}
};
return predicate.test(map);
}
public static void main(String[] args) {
Map map = new HashMap();
//使用predicate方法
System.out.println( isNullOrEmpty(map));
}
consumer
//客户需要购物
public static void shopping(double x, Consumer consumer) {
consumer.accept(x);
}
public static void main(String[] args) {
Consumer<Double> c1 = (x) -> {
System.out.println("小李花了" + x + "元,买了一辆车");
};
shopping(20000, c1);
Consumer<Double> c2 = (x) -> {
System.out.println("小明花了" + x + "元,买了一堆化妆品");
};
shopping(50000, c2);
}
总结
前五个对应一个输入一个输出,没有输出是消费者,没有输入是生产者。
方法引用示例
import java.util.function.*;
class Dog{
private String name= "哮天犬";
private int food =10;
public static void bark(Dog dog){
System.out.println(dog +"叫了");
}
public Dog (String name){
this.name = name;
}
public Dog ( ){
}
@Override
public String toString() {
return this.name;
}
/**
* 吃狗粮,静态方法
* 接下来我们对这成员方法进行成员引用,成员引用套路都是一样的,就是分析他的输入和输出,输入一个int输出一个int
* @param this JDK默认会把当前实例传入到非静态方法,参数名为this,位置是第一个
* @param num
* @return
*/
public int eat(Dog this,int num){
System.out.println("吃了" +num+ "斤狗粮");
this.food = this.food - num;
return this.food;
}
}
public class MethodRefrenceDemo {
public static void main(String[] args) {
// 这就是一个lambda表达式,箭头左边是输入参数,箭头右边是函数的执行体
// 当你的函数执行体里面只有一个函数进行调用,而且函数的参数和传入参数是一样的话,我们就缩写成
Consumer<String> consumer = s-> System.out.println(s);
consumer.accept("接收数据1");
// 方法引用的方式
Consumer<String> consumer2 = System.out::println;
consumer2.accept("接受数据");
//静态方法的方法引用
Dog dog = new Dog();
Consumer<Dog> consumer3 = Dog::bark;
consumer3.accept(dog);
// 非静态方法,使用对象实例来引用
Function<Integer,Integer> funtion = dog::eat;
System.out.println("还剩下" +funtion.apply(2)+ "斤");
// 演进 1 当输入和输入都是相同类型则可以用一元函数
UnaryOperator<Integer> unaryOperator = dog::eat;
System.out.println("还剩下" +unaryOperator.apply(2)+ "斤");
// 演进2 :因为我们这都是Integer,所以JDK8针对类型也有对应的函数接口
IntUnaryOperator intUnaryOperator = dog::eat;
System.out.println("还剩下" +intUnaryOperator.applyAsInt(2)+ "斤");
// 静态方法和成员方法有一个区别是什么呢?
// 成员方法可以调用this, 静态方法不能调用this ,那jdk是怎样实现的的,他是在方法里面传递了this
Dog dog1 = new Dog();
dog1.eat(23);
// 使用类名来方法引用
BiFunction<Dog,Integer,Integer> eatFunction = Dog::eat;
System.out.println(eatFunction.apply(dog1,2));
// 构造函数的方法引用
// 无构造参数
// 我们dog里面没有对应的构造方法,我们默认是空的构造方法
// 这也就是传入是空的,返回一个结果也就生产者
Supplier<Dog> supplier = Dog::new;
System.out.println("创建了新对象:" +supplier.get());
// 有参数的构造方法
// 我们这里是和上面一样的,我们的JDK会自动找到有对应参数的
Function<String,Dog> fun = Dog::new;
System.out.println(fun.apply("警犬"));
}
}
lambda表达式的类型推断
之前说过lambda表达式是一个匿名函数,它最后返回来的是实现某个接口的对象。
@FunctionalInterface
interface IMath{
int add(int x,int y);
}
@FunctionalInterface
interface IMath2{
int add(int x,int y);
}
public class TypeDemo {
public static void main(String[] args) {
// 变量类型定义
IMath lambda = (x,y) -> x +y;
// 强转
Object lambda2 = (IMath) (x,y) -> x + y;
//通过返回类型
IMath createLambda = createLambda();
// 实际我们编写的是后都是在方法里面调用的
TypeDemo demo = new TypeDemo();
// demo.test((x,y) ->x +y);
// 注意:我们这里如果重载的话就会有问题,此时我们可以
//通过强制类型转化
demo.test((IMath) (x,y) ->x +y);
}
public void test(IMath iMath){
}
public void test(IMath2 iMath){
}
private static IMath createLambda() {
return (x,y) -> x +y;
}
}
变量引用
consumer是作为值传递的外面定义的变量
/**
* Lambda表达式是实现某个接口的匿名类,那他引用变量和我们匿名类是相同的
*/
public class VarDemo {
public static void main(String[] args) {
//此处默认加了一个final
String str = "欢迎您";
//str = "SSS";
Consumer<String> consumer = s -> System.out.println(s + str);
consumer.accept("北京");
// jdk8之前内部类引用外面的变量,这变量必须声明为final类型,
// 那jdk8里面默认可以不写,他默认增加了final
// 匿名类引用外面的变量必须是final
// 因为我们java方法传参的形式,是传的值而不是引用
List<String> list =new ArrayList<>();
Consumer<String> consumer2 = s -> System.out.println(s + list);
consumer.accept("北京");
}
}
Stream流
内部迭代和外部迭代
int[] nums = {1,2,3};
// 外部迭代
int sum1 = 0;
for(int i : nums){
sum1 += i;
}
System.out.println("结果为:" + sum1);
// 使用Stream的内部迭代
//比较简短
int sum2 = IntStream.of(nums).sum();
System.out.println("结果为:" + sum2);
中间操作和终止操作
public static void main(String[] args) {
int[] nums = {1,2,3};
// map 是中间操作 (返回Stream的操作)
// sum 是终止操作
int sum3 = IntStream.of(nums).map(StreamDemo1::doubleNum).sum();
System.out.println("结果为:" + sum3);
System.out.println("惰性求值就是最终没有调用的情况下,中间操作不会执行");
//此处不会输出执行了乘以2
IntStream.of(nums).map(StreamDemo1::doubleNum);
}
public static int doubleNum(int i){
System.out.println("执行了乘以2");
return i * 2;
}
Stream流编程-创建流的方式
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 从集合创建流
list.stream();
list.parallelStream();
// 从数组创建流
Arrays.stream(new int[]{1,2,3,4,5});
// 创建数字流
IntStream.of(1,2,3,4);
IntStream.rangeClosed(1,10);
//使用random创建一个无限流
new Random().ints().limit(10);
Random random = new Random();
// 自己产生流
Stream.generate(() -> random.nextInt()).limit(20);
}
Stream流编程-中间操作
无状态操作:当前的操作和其他前后的操作无关
有状态操作:当前结果依赖于其他的结果
String str = "my name is Jack";
// 把每个单词的长度调用出来
Stream.of(str.split(" ")).map(s ->s.length()).forEach(System.out::println);
//筛选出长度大于2的单词
Stream.of(str.split(" ")).filter(s -> s.length() > 2)
.map(s -> s.length()).forEach(System.out::println);
// flatMap A -> B属性(是个集合),最终得到所有的A元素里面的所有B属性集合
// intStream/longStream 并不是Stream的子类,所以要进行装箱
Stream.of(str.split(" ")).flatMap(s -> s.chars().boxed() ).forEach(System.out::println);
Stream.of(str.split(" ")).flatMap(s -> s.chars().boxed()).forEach(
i -> System.out.println((char)i.intValue()));
// // peek 用于debug,是中间操作和forEach 是终止操作
System.out.println("=============peek===============");
Stream.of(str.split(" ")).peek(System.out::println).forEach(System.out::println);
// limit 使用,主要用于无限流
// 1、我们不做限制的看看他是否出现异常,这里没有异常,好像一直不会结束
new Random().ints().forEach(System.out::println);
new Random().ints().filter(i -> i > 100 && i < 1000).limit(3)
.forEach(System.out::println);
Stream流编程-终止操作
// 使用并行流,我们打印一下发现是乱序的
str.chars().parallel().forEach(i -> System.out.print((char)i));
System.out.println();
// // 使用 forEachOrdered保证顺序
str.chars().parallel().forEachOrdered(i -> System.out.print((char)i));
//第二行:collect/toArray我们可以看到他是一个收集的作用,他可以转成数组或者集合
// 收集到list
List<String> list = Stream.of(str.split(" ")).collect(Collectors.toList());
// 第三个是reduce操作,reduce就是减少的意思,他就是将流合成一个数据,
//使用reduce拼接字符串
// 这里返回一个optional 这也是新jdk8添加的, optional是选项的意思,避免自己调用一些空判断,所以我们一般是这样使用
Optional<String> letters = Stream.of(str.split(" "))
.reduce((s1, s2) -> s1 + "|" + s2);
System.out.println(letters.orElse(""));
// 带初始化值的reduce
String reduce = Stream.of(str.split(" "))
.reduce("", (s1, s2) -> s1 + "|" + s2);
System.out.println(reduce);
//这就是字符串总长度
Integer length = Stream.of(str.split(" ")).map(s ->s.length())
.reduce(0, (s1, s2) -> s1 + s2);
System.out.println(length);
// 使用max
Optional<String> max = Stream.of(str.split(" ")).max((s1, s2) -> s1.length() - s2.length());
System.out.println(max.get());
// 使用findFirst短路操作
OptionalInt any = new Random().ints().findAny();
System.out.println(any.getAsInt());
OptionalInt first = new Random().ints().findFirst();
System.out.println(first.getAsInt());
// allMatch
Student stu1 = new Student( 19, "张三");
Student stu2 = new Student( 23, "李四");
Student stu3 = new Student( 28, "王五");
List<Student> students = new ArrayList<>();
students.add(stu1);
students.add(stu2);
students.add(stu3);
//判断学生年龄是否都大于18
boolean flag = students.stream().allMatch(stu -> stu.getAge() > 18);
System.out.println(flag);
并行流
public static void debug(int i){
System.out.println(Thread.currentThread().getName() + " debug " + i);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void debug2(int i){
System.out.println(" debug2 " + i);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//1、调用 顺序进行调用(串行)
IntStream.range(1,100).peek(StreamDemo5::debug).count();
//2、调用 paralled产生一个并行流(并行)
IntStream.range(1,100).parallel().peek(StreamDemo5::debug).count();
//3、现在要实现一个这样的效果: 先并行,再串行
// 多次调用 parallel / sequential 以最后一次为准,下例子是串行的
IntStream.range(1,100)
// 调用paralled产生并行流
.parallel().peek(StreamDemo5::debug)
// // 调用sequential 产生串行流
.sequential().peek(StreamDemo5::debug2)
.count();
// 4.并行流使用的线程池:ForkJoinPool.commonPool
IntStream.range(1,100).parallel().peek(StreamDemo5::debug).count();
public static void debug(int i){
System.out.println(Thread.currentThread().getName() + " debug " + i);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 5、我们现在并行流都是用的同一个线程池,那就会有一下问题:
// 多个并行任务用同一个线程池,就会有排队等待的情况,这时候我们就需要自己创建线程池
// 防止任务被阻塞
ForkJoinPool pool = new ForkJoinPool(20);
pool.submit(() -> IntStream.range(1,100).parallel().peek(StreamDemo5::debug).count());
// 处理完毕线程池会自动关闭
pool.shutdown();
// 我们实现将自己的任务放到自己并行线程池里面,我们测试一下
// 发现控制台并没有任何日志打印出来
// 这是因为主线程已经停了,我们线程池是守护线程的关系,所以他也自动退出了,那我们需要让我们的主线程不要退出
synchronized (pool){
try {
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 运行之后发现用的是我们自己的线程池 ForkJoinPool-1
收集器
public class Person {
private String name;
private int age;
private Gender gender;
private Grade grade;
public Person(String name, int age, Gender gender, Grade grade) {
this.name = name;
this.age = age;
this.gender = gender;
this.grade = grade;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
", 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 Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public Grade getGrade() {
return grade;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
enum Gender{
MALE,FEMALE
}
enum Grade{
ONE,TWO,THREE,FOUR;
}
public static void main(String[] args) {
List<Person> peoples = Arrays.asList(
new Person("小明",10,Gender.MALE,Grade.ONE),
new Person("小林",12,Gender.FEMALE,Grade.ONE),
new Person("李刚",13,Gender.MALE,Grade.TWO),
new Person("小花",14,Gender.MALE,Grade.TWO),
new Person("小兰",5,Gender.FEMALE,Grade.THREE),
new Person("天涯",16,Gender.MALE,Grade.ONE),
new Person("善缘",16,Gender.MALE,Grade.TWO),
new Person("谷歌",43,Gender.FEMALE,Grade.FOUR),
new Person("黎明",33,Gender.MALE,Grade.ONE)
);
// 1、得到所有学生的年龄列表
// 或者我们可以转化为:Collectors.toSet() ,或者指定的集合类型:他默认是hashSet我们如果用TreeSet
// 可以如下:Collectors.toCollection(TreeSet::new )
List<Integer> ages = peoples.stream().map(s -> s.getAge()).collect(Collectors.toList());
System.out.println("所有学生的年龄 :" + ages);
// 1、统计汇总信息
IntSummaryStatistics ageSummaryStatistics = peoples.stream().collect(Collectors.summarizingInt(Person::getAge));
System.out.println("年龄汇总:" + ageSummaryStatistics);
//2、分块
Map<Boolean, List<Person>> genders = peoples.stream().
collect(Collectors.partitioningBy(s -> s.getGender() == Gender.MALE));
//
//System.out.println( "男女学生列表:" + genders);
// 上述打印不直观
MapUtils.verbosePrint(System.out,"男女学生列表",genders);
// 3、分组
Map<Grade, List<Person>> grades = peoples.stream()
.collect(Collectors.groupingBy(Person::getGrade));
MapUtils.verbosePrint(System.out,"学习班级列表",grades);
// 4、得到所有班级的个数
Map<Grade, Long> gradesCount = peoples.stream()
.collect(Collectors.groupingBy(Person::getGrade,Collectors.counting()));
MapUtils.verbosePrint(System.out,"班级学生个数列表:",gradesCount);
}
}
引入pom
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
</dependencies>