> SpringBoot2.0不容错过的新特性 WebFlux响应式编程
---
#### 第一章 : 课程介绍
#### 第二章 : 函数式编程和Lambda表达式
###### 1.找出数组中最小的值
- 1 .命令式编程
```
public class TestController {
private static final int LENGTH = 10;
private static final int BOUND = 99;
public static void main(String[] args) {
int min = Integer.MAX_VALUE;
int[] result = arrayListNum();
System.out.println(Arrays.toString(result));
for (int i : result) {
if (i < min) {
System.out.println(min);
min = i;
}
}
System.out.println("the min is :" + min);
}
public static int[] arrayListNum() {
int[] ints = new int[LENGTH];
for (int i = 0; i < LENGTH; i++) {
ints[i] = new Random().nextInt(BOUND);
}
return ints;
}
}
```
- 2.函数式编程
```
int asInt = IntStream.of(result).min().getAsInt();
System.out.println("最小值:" + asInt);
如果是下面这样:
int asInt = IntStream.of(result).parallel().min().getAsInt();
意思是:如果这个result数组有几亿条,关键字 .paraller() 会对这个数组进行拆分,这个流会并行执行,自己内部定义线程池和多线程和数据拆分
```
###### 2.创建多线程的方式
- 1.JDK8之前的
```
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("ok");
}
}).start();
```
- JDK8之后的 (用lambda表达式)
```
new Thread(() -> System.out.println("ok")).start();
// 此种方式 不用去管内部实现了什么接口,是哪种方式创建的
```
###### 3.关于接口的Lambda写法
```
public class LambdaDemo1 {
public static void main(String[] args) {
Interface1 i1 = i -> i * 2;
/** 上下为两种常用写法; */
Interface1 i2 = (i) -> i * 2;
/** 可以指明类型 */
Interface1 i3 = (int i) -> i * 3;
Interface1 i4 = (int i) -> {
System.out.println("只有一行的之后不用加花括号,并且默认是return返回的");
return i * 4;
};
}
}
// @FunctionalInterface
interface Interface1 {
int doubleNum(int i);
}
```
**备注:**
- 1 .如果要这样用Lambda的话,上面的接口里面 ==只允许有一个要实现的方法,这一个方法也称为“默认方法!”== ,(注意不是“默认实现方法”,默认实现方法叫做default .....)
- 2 .不加<code>@FunctionalInterface</code>这个注解也是可以运行的,但是还是建议加上。为了代码的完整性,这个注解也可检查下面的代码的写法是否正确。比如下面是否有多个方法,有此注解就会报错表示不能多个方法(特例下面4除外)
- 3 .这种设计原理是“单一职责模式”,即是将接口设计的越小越细越好!做到一个接口只干一件事情!接口可以多重继承,所以拆的很细只要多个继承了,使用效果也不会比原先一个接口里面有很多方法的那种差!
- 4 .JDK8之后,接口里面除了上面讲的==可以有一个“默认方法”之外,还有一个“默认实现方法”==,这个方法的存在,是不会让上面的讲的报错出现的,因为是内置的方法!
- eg: ==default==,可以当做类中方法
```
public class LambdaDemo1 {
public static void main(String[] args) {
Interface1 i1 = i -> i * 2;
Interface1 i2 = (i) -> i * 2;
i2.add(3 ,5);
}
}
@FunctionalInterface
interface Interface1 {
int doubleNum(int i);
default int add(int x, int y) {
return x + y;
}
}
```
- ps:这种写法需要注意的是,在接口多重继承的时候可能会选择实现 继承的哪一个接口里面的默认方法
```
@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 Interface2, Interface1 {
@Override
default int add(int x, int y) {
return 0;
}
}
```
###### 4.函数接口
*特点*
- 1 .可以尽可能少的去定义接口
- 2 .可以实现链式编程
普通示例:
```
interface MoneyFormat {
String format(int i);
}
class MyMoney {
private int money;
public MyMoney(int money) {
this.money = money;
}
public void printMoney(MoneyFormat moneyFormat) {
System.out.println("我的存款:" + moneyFormat.format(money));
}
}
public class MoneyDemo {
public static void main(String[] args) {
MyMoney myMoney = new MyMoney(9999999);
myMoney.printMoney(i -> new DecimalFormat("#,###").format(i));
}
}
```
经过改造,并且加链式编程:
```
class MyMoney {
private int money;
public MyMoney(int money) {
this.money = money;
}
public void printMoney(Function<Integer, String> moneyFormat) {
System.out.println("我的存款:" + moneyFormat.apply(money));
}
}
public class MoneyDemo {
public static void main(String[] args) {
MyMoney myMoney = new MyMoney(9999999);
Function<Integer, String> moneyFormat = i -> new DecimalFormat("#,###").format(i);
myMoney.printMoney(moneyFormat.andThen(s -> "人民币 " + s + "元"));
}
}
```
- 结果:Lambda不关心是哪个接口,也不关系是实现了哪个方法。lambda只关系输入的是啥,输出的是啥。所以上面的上面的接口里面<code>String format(int i);</code>表示输入的是<code>int</code>,输出的是<code>String</code>
- 所以:改造的方法中就没有了接口,就只有了lambda中引入的只有输入输出的<code>Function</code><Integer, String>。这样就省掉了一个接口
- 并且:可以链式编程,此处就是链式编程加了一些前缀和后缀
###### 5. 接口:
接口 | 输入参数 | 返回类型 | 说明
---|---|---|---
Predicate<T> | T | boolean | 断言
Consumer<T> | T | / | 消费一个数据
Function<T,R> | T | R | 输入T输出R的函数
Supplier<T> | / | T | 提供一个数据
UnaryOperator<T> | T | T | 一元函数(输出输入类型相同)
BiFunction<T,U,R> | (T,U) | R | 2个输入的函数
BinaryOperator<T> | (T,T) | T | 二元函数(输出输入类型相同)
- eg:
```
public class LamTest {
public static void main(String[] args) {
/** 断言函数接口 **/
Predicate<Integer> predicate = i -> i > 0;
System.out.println(predicate.test(10));
===> true
/** 消费函数接口 **/
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("输入的数据");
===> 输入的数据
}
}
```
上面的尽量使用带有类型前缀的接口,比如原先的<code>Predicate<Integer></code>可以替换成<code>IntPredicate</code>,<code>IntConsumer</code>
###### 6. 方法引用
- 1.改造上面的“消费者接口”
```
原本为:
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("输入的数据");
```
```
改装为方法引用:
Consumer<String> consumer = System.out::print;
consumer.accept("接收到的数据");
```
作者的:
```
class Dog {
private String name = "哮天犬";
/** 默认10斤狗粮 */
private int food = 10;
public Dog() {
}
/** 带参数的构造函数
* @param name
*/
public Dog(String name) {
this.name = name;
}
/** 狗叫,静态方法 */
public static void bark(Dog dog) {
System.out.println(dog + "叫了");
}
/** 吃狗粮 JDK <p>
* 默认会把当前实例传入到非静态方法,参数名为this,位置是第一个;
*
* @param num
* @return 还剩下多少斤
*/
public int eat(int num) {
System.out.println("吃了" + num + "斤狗粮");
this.food -= num;
return this.food;
}
@Override
public String toString() {
return this.name;
}
}
public class MethodRefrenceDemo {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(3);
// 方法引用
Consumer<String> consumer = System.out::println;
consumer.accept("接受的数据");
// 静态方法的方法引用
Consumer<Dog> consumer2 = Dog::bark;
consumer2.accept(dog);
// 非静态方法,使用对象实例的方法引用
// Function<Integer, Integer> function = dog::eat;
// UnaryOperator<Integer> function = dog::eat;
IntUnaryOperator function = dog::eat;
// dog置空,不影响下面的函数执行,因为java 参数是传值
dog = null;
System.out.println("还剩下" + function.applyAsInt(2) + "斤");
//
// // 使用类名来方法引用
// 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("旺财"));
// 测试java变量是传值还是穿引用
List<String> list = new ArrayList<>();
test(list);
System.err.println(list);
}
private static void test(List<String> list) {
list = null;
}
}
```
###### 7. 类型判断
```
public class TypeDemo {
public static void main(String[] args) {
/** 常规 **/
IMath lambda = (x, y) -> x + y;
/** 数组形式 **/
IMath[] lambda1 = {(x, y) -> x + y};
/** 强转 **/
Object lambda2 = (IMath) (x, y) -> x + y;
/** 通过返回类型1 */
IMath createDlambda = createLambda();
/** 通过返回类型2 */
TypeDemo typeDemo = new TypeDemo();
typeDemo.test(((x, y) -> x + y));
}
public static IMath createLambda() {
return (x, y) -> x + y;
}
public void test(IMath iMath) { }
}
@FunctionalInterface
interface IMath {
int add(int x, int y);
}
```
###### 8.变量引用
- 这个例子,类似于,<code>内部类</code>引用外部内的变量的时候,这个变量是不能再次赋值的。并且建议这个变量是用<code>final</code>来修饰。
- 原因:下面的第一个str是指向的"init_string",第二个str也是指向的这个堆对象。但是如果第一个str被重新赋值后,代码会报错,是因为第二个Str的地方根本不会引用这个新的赋值,而是依然会引用原本的"init_string"这个堆对象。
```
public class VarDemo {
public static void main(String[] args) {
String str = "init_string";
// 建议 final String str = "init_string";
// 不能再次赋值: str = "新的内容";
Consumer<String> consumer = s -> System.out.println(s + " " + str);
consumer.accept("消费这个字符串");
}
}
```
###### 9. 级联表达式和柯里化
- 拆分: 箭头左边是输入(x),箭头右边是输出(y->x+y) ; 右边的这个输入又是一个函数,即再次: 输入y , 输出 x+y
```
public class CurryDemo {
public static void main(String[] args) {
Function<Integer, Function<Integer, Integer>> fun = x -> y -> x + y;
System.out.println(fun.apply(2).apply(3));
}
}
```
#### 第三章 : Stream 流 编程
#### 第四章 :reactive stream 相应式流
#### 第五章 :webFlux服务端开发讲解
#### 第六章 :webFlux客户端声明式restClient框架开发讲解
版权归属:
Administrator
许可协议:
本文使用《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》协议授权
评论区