diff --git "a/java\345\237\272\347\241\200/assets/stream/Sink\346\216\245\345\217\243.png" "b/java\345\237\272\347\241\200/assets/stream/Sink\346\216\245\345\217\243.png" new file mode 100644 index 0000000000000000000000000000000000000000..bcabc7d17768156cab3a29a6d439baa2ea71625c Binary files /dev/null and "b/java\345\237\272\347\241\200/assets/stream/Sink\346\216\245\345\217\243.png" differ diff --git "a/java\345\237\272\347\241\200/assets/stream/Stream\346\211\247\350\241\214\350\277\207\347\250\213.png" "b/java\345\237\272\347\241\200/assets/stream/Stream\346\211\247\350\241\214\350\277\207\347\250\213.png" new file mode 100644 index 0000000000000000000000000000000000000000..143032167ad635b0b3ac791c4ed57d19c855630e Binary files /dev/null and "b/java\345\237\272\347\241\200/assets/stream/Stream\346\211\247\350\241\214\350\277\207\347\250\213.png" differ diff --git "a/java\345\237\272\347\241\200/assets/stream/Stream\347\261\273\345\233\276\347\273\223\346\236\204.png" "b/java\345\237\272\347\241\200/assets/stream/Stream\347\261\273\345\233\276\347\273\223\346\236\204.png" new file mode 100644 index 0000000000000000000000000000000000000000..670e06cdf22317ee75a535da70557448f8772e87 Binary files /dev/null and "b/java\345\237\272\347\241\200/assets/stream/Stream\347\261\273\345\233\276\347\273\223\346\236\204.png" differ diff --git "a/java\345\237\272\347\241\200/stream API.md" "b/java\345\237\272\347\241\200/stream API.md" new file mode 100644 index 0000000000000000000000000000000000000000..b050f0c613fd16d0bd42b8792736d506aae612eb --- /dev/null +++ "b/java\345\237\272\347\241\200/stream API.md" @@ -0,0 +1,75 @@ +## stream + +## 介绍 + +* Java 8引入了全新的 Stream API +* stream 是对集合对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。 + +## 使用 + +处理逻辑:数据源(datasource) --> 数据转换(中间操作) -->数据转换(中间) -->执行操作获取结果(结束操作) + +* 中间操作 + * 中间操作只是对操作进行了记录,只有结束操作才会触发实际的计算(即惰性求值),这也是Stream在迭代大集合时高效的原因之一。 + * 中间操作可以分为无状态(Stateless)操作与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响;后者是指该操作只有拿到所有元素之后才能继续下去。 + +* 结束操作 + * 结束操作可以分为短路与非短路操作,前者是指遇到某些符合条件的元素就可以得到最终结果;而后者是指必须处理所有元素才能得到最终结果。 + +## 特性 + +1. stream不存储数据 +2. stream不改变源数据 +3. stream的延迟执行特性:在结束操作执行之前,所有中间操作都没执行,只有当结束操作执行时才会触发之前的中间操作 + +## 实现 + +1. 采用了流水线的方式,Stream中的每个操作都对应一个Pipeline对象,每个Pipeline对象都含有sourceStage、previousStage(upStream)、nextStage(downStream)三个属性。最终整个Stream操作会转化为一个Pipeline的双向链表。 +2. 首先stream()函数会产生一个Head,其不含任何操作,存储着原始数据。 + 接下来每个操作都会产生一个Pipeline对象,无状态操作对应StatelessOp对象,有状态操作对应StatefullOp对象。 +3. 每个Pipeline对象创建(除了Head对象)时,都会将this指针传入作为上源流,因为例如xxx.stream().map().filter().collect()这一串流操作,每一步都会创建一个新的Pipeline对象,然后下一步由上一步创建的新对象调用,this刚好指向上一步的对象,stream()会创造一个Head对象,然后Head对象再调用map创建一个StatelessOp对象,将this传入,this指向Head。 +4. 直到结束操作,结束操作会使用递归的方式不断调用onWrapSink函数,由下向上构建一个Sink链,然后执行流操作。所以所有的中间操作只是会被记录,只有遇到结束操作时,才会执行真正的所有操作。 +5. 结束操作对应TerminalOp,最终会执行evaluateSequential方法,从head开始依次执行。 + +![Stream 类图结构](./assets/stream/Stream类图结构.png) + +### 如何记录操作 + +* 流式操作将每一步都对应一个Pipeline对象(Stage),其由三部分组成,即<数据源,操作,回调函数> +* 将具有先后顺序的各个Stage连到一起,就构成了整个流水线 +* Collection.stream()方法得到Head,然后调用一系列的中间操作,不断产生新的Stream,这些Stream对象以双向链表的形式组织在一起,构成整个流水线。由于每个Stage都记录了前一个Stage和本次的操作以及回调函数,依靠这种结构就能建立起对数据源的所有操作。 + +![Stream 执行过程](./assets/stream/Stream执行过程.png) + +### 如何叠加操作 + +* 前面的Stage并不知道后面Stage到底执行了哪种操作,以及回调函数是哪种形式。换句话说,只有当前Stage本身才知道该如何执行自己包含的动作。这就需要有某种协议来协调相邻Stage之间的调用关系,即Sink接口 + +![Sink接口](./assets/stream/Sink接口.png) + +* 每个Stage都会将自己的操作封装到一个Sink里,前一个Stage只需调用后一个Stage的accept()方法即可,并不需要知道其内部是如何处理的。 +* 对于有状态的操作,Sink的begin()和end()方法也是必须实现的 +* 执行时只需要从流水线的head开始对数据源依次调用每个Stage对应的Sink.{begin(), accept(), cancellationRequested(), end()} +* 实际上Stream API内部实现的的本质,就是如何重写Sink的这四个接口方法 + +### 如何执行操作 + +* 结束操作会创建一个包装了自己操作的Sink,这个Sink只需要处理数据而不需要将结果传递给下游的Sink +* Sink AbstractPipeline.opWrapSink(int flags, Sink downstream)方法返回一个新的包含了当前Stage代表的操作以及能够将结果传递给downstream的Sink对象 +* 只要从流水线的最后一个Stage开始,不断调用上一个Stage的opWrapSink()方法直到最开始(不包括stage0,因为stage0代表数据源,不包含操作),就可以得到一个代表了流水线上所有操作的Sink + +### 执行结果在哪 + +有返回结果的Stream结束操作如下: + +| 返回类型 | 对应的结束操作 | +| :------: | :-------------------------------: | +| boolean | anyMatch() allMatch() noneMatch() | +| Optional | findFirst() findAny() | +| 归约结果 | reduce() collect() | +| 数组 | toArray() | + +* 对于返回boolean或者Optional的操作,由于返回一个值,只需要在对应的Sink中记录这个值,等到执行结束时返回就可以了 +* 对于归约操作,最终结果放在用户调用时指定的容器中。 +* 对于返回是数组的情况,毫无疑问的结果会放在数组当中。但在最终返回数组之前,结果其实是存储在一种叫做Node的数据结构中的。Node是一种多叉树结构,元素存储在树的叶子当中,并且一个叶子节点可以存放多个元素。这样做是为了并行执行方便。 + diff --git "a/java\345\237\272\347\241\200/\345\207\275\346\225\260\345\274\217\346\216\245\345\217\243\345\222\214lambda.md" "b/java\345\237\272\347\241\200/\345\207\275\346\225\260\345\274\217\346\216\245\345\217\243\345\222\214lambda.md" new file mode 100644 index 0000000000000000000000000000000000000000..bd3506cf67ac2f36cc62abb80816a03bcbe1d64e --- /dev/null +++ "b/java\345\237\272\347\241\200/\345\207\275\346\225\260\345\274\217\346\216\245\345\217\243\345\222\214lambda.md" @@ -0,0 +1,295 @@ +# 函数式接口和lambda + +## 为什么要引入lambda表达式 + +* java 是一门纯面向对象的语言,如果我们需要传递某个可以复用的代码块,我们必须将代码块逻辑抽离到对象中。 +* java 8 引入lambda后,java 也有了直接处理代码块的能力,在一定程度上支持了函数式编程。 + +## lambda表达式语法 + +基本表达式结构 : `(参数) -> {代码块}` + +* 无参时:`() -> {System.out.println("test")}` +* 代码块只有一条语句:`() -> System.out.println("test")` + +* 如果能通过上下文推导出参数类型,可以忽略参数类型:`Comparator comp = (i1 , i2) -> i1-i2;` + +## 函数式接口 + +```java +@FunctionalInterface +interface IFunctionTest { + public void print(T x); +} +``` + +* 为了能够传递一个方法作为参数传递给另一个方法,java 8 引入了包含一组接口的 java.util.function +* 函数式接口编译完之后依然是一个接口,这个接口具有唯一的一个抽像方法 +* @FounctionalInterface 注解:一是做标识,二是确保接口只有一个抽象方法 +* 函数式接口中只有一个抽象方法,但是可以定义默认方法、静态方法、java.lang.Object 里的 public 方法 + +## Function接口 + +```java +@FunctionalInterface +interface Function { + R apply(T t); +} + +public class TestFunction { + public static void main(String[] args) { + Function function = s -> s + "!"; + System.out.println(function.apply("HelloWorld")); + } +} +``` + +## Supplier接口 + +```java +@FunctionalInterface +public interface Supplier { + T get(); +} + +public class TestSupplier { + public static void main(String[] args) { + Supplier appleSupplier = Apple::new; + System.out.println("--------"); + appleSupplier.get(); + } +} +class Apple{ + public Apple() { + System.out.println("创建实例"); + } +} +``` + +## Consumer接口 + +```java +@FunctionalInterface +public interface Consumer { + + void accept(T t); + + default Consumer andThen(Consumer after) { + Objects.requireNonNull(after); + return (T t) -> { accept(t); after.accept(t); }; + } +} + +public class TestConsumer { + public static void main(String[] args) { + Consumer consumer = (t) -> { + System.out.println(t*3); + }; + Consumer consumerAfter = (s) -> { + System.out.println("之后执行:"+s); + }; + consumer.andThen(consumerAfter).accept(5); + } +} +``` + +consumer的具体的使用场景就是可以提前记录我们的某些操作,然后在后面再去执行 + +比如说:当我们在a方法中,需要把某些参数赋值给一个Integer类型的对象,而该对象只有在b方法才能赋值,那么我们可以在a方法中使用consumer记录我们要执行的操作,再把consumer作为参数传递到b方法执行 + +## Predicate接口 + +```java +@FunctionalInterface +interface Predicate { + boolean test(T t); + // 还有四个默认实现的方法: and(), negate(), or(), isEqual() +} + +public class TestPredicate { + public static void main(String[] args) { + Predicate predicate = s -> s.isEmpty(); + System.out.println(predicate.test("HelloWorld")); + } +} +``` + +使用场景:在一个公共函数内,大部分逻辑是通用的,但是一小部分判断逻辑是不一样的,可以使用Predicate作为公共函数的入参,将那一小部分判断逻辑通过Lambda表达式的方式传入公共函数。 + +Spring Cloud Gateway 将路由匹配作为 Spring WebFlux HandlerMapping 基础架构的一部分。 + +Spring Cloud Gateway 创建 Route 对象时, 使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。 + +Spring Cloud Gateway 包括许多内置的 Route Predicate 工厂。所有这些 Predicate 都与HTTP请求的不同属性匹配。多个 Route Predicate 工厂可以进行组合。 + +常见的有:After Route Predicate、Before Route Predicate、Between Route Predicate、Cookie Route Predicate、Header Route Predicate、Host Route Predicate、Method Route Predicate、Path Route Predicate、Query Route Predicate + +## 原理 + +```java +public class LambdaTest { + public static void printString(String s, Print print) { + print.print(s); + } + public static void main(String[] args) { + printString("test", (x) -> System.out.println(x)); + } +} + +@FunctionalInterface +interface Print { + public void print(T x); +} +``` + +通过 javac 编译 LambdaTest.java 文件,会生成 LambdaTest.class、Print.class 两个 .class 文件 + +反编译一下代码如下: + +```java +Compiled from "LambdaTest.java" +public class LambdaTest { + public LambdaTest(); + public static void printString(java.lang.String, Print); + public static void main(java.lang.String[]); + // 编译器会根据 Lambda 表达式生成一个私有的静态函数 + private static void lambda$main$0(java.lang.String); +} +``` + +最终代码还原后 + +```java +public class LambdaTest { + public static void PrintString(String s, Print print) { + print.print(s); + } + + public static void main(String[] args) { + PrintString("test", new LambdaTest$$Lambda$1()); + } + + private static void lambda$main$0(String x) { + System.out.println(x); + } + + static final class LambdaTest$$Lambda$1 implements Print { + public void print(Object obj) { + LambdaTest.lambda$main$0((String) obj); + } + private LambdaTest$$Lambda$1() { + } + } + +} + +@FunctionalInterface +interface Print { + public void print(T x); +} +``` + +### 小结 + +1. 函数式接口和 Lambda 表达式在类编译时,会生成一个私有静态方法+一个内部类; +2. 在内部类中实现了函数式接口,在实现接口的方法中,会调用编译器生成的静态方法; +3. 在使用 Lambda 表达式的地方,通过传递内部类实例,来调用函数式接口方法。 + +实际上,就是实现了传递函数指针的功能。 + +## 高阶函数 + +高阶函数是一个能接受函数作为参数或者把函数当作返回值的函数 + +```java +public interface FuncSS extends Function {} + +public class test { + static FuncSS produce() { + return s -> s.toLowerCase(); + } +} +``` + +## 闭包 + +lambda表达式中使用了其函数作用域外的变量 + +```java +public class Closure1 { + int i; + public IntSupplier makeFun(int x) { + return () -> x + i++; + } +} +``` + +如果i是makeFun()中的局部变量 + +```java +// It's OK +public class Closure2 { + public IntSupplier makeFun(int x) { + int i = 0; + return () -> x + i; + } +} + +// x和i报同一个错误 +// error: local variables referenced from a lambda rxpression must be final or effectively final +public class Closure3 { + public IntSupplier makeFun(int x) { + int i = 0; + return () -> x++ + i++; + } +} + +public class Closure4 { + public IntSupplier makeFun(final int x) { + FINAL int i = 0; + return () -> x++ + i++; + } +} +``` + +如果一个变量在初始化之后其值永远不会改变,它就是实际上的最终变量(effectively final) + +变量捕获:局部类和匿名类都可以访问**最终(final)**或**实际上的最终(effectively final)**的封闭块的局部变量和参数 + +Closure1中i的修改既不是最终变量,也不是实际上的最终变量,但是没有引发编译错误。这是因为i是外部类的成员,拥有独立的生命周期,不需要捕获,在之后lambda表达式被调用时它依然存在 + +匿名内部类 + +```java +public class AnonymousClosure { + IntSupplier makeFun(int x) { + int i = 0; + return new IntSupplier() { + public int getInt() { + return x + i; + } + } + } +} +``` + +## 柯里化 + +柯里化是指将一个接收多个参数的函数转变为一系列只接受一个参数的函数,目的是能够通过一个参数来创建一个新函数 + +```java +public class CurryingAndPartials { + static String uncurried(String a, String b) { + return a + b; + } + + public static void main(String[] args) { + System.out.println(uncurried("Hello", "World")); + + Function> sum = a -> b -> a + b; + Function hello = sum.apply("Hello"); + System.out.println(hello.apply("World")); + } +} +``` +