# functional-programming-ideas **Repository Path**: lifutian66/functional-programming-ideas ## Basic Information - **Project Name**: functional-programming-ideas - **Description**: 函数式编程思想个人随想 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-08-22 - **Last Updated**: 2022-09-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 函数式编程 ### 1.定义 函数式编程,是一种**编程范式**(函数式、指令式、过程式、面向对象编程等等),它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。源自[阿隆佐·邱奇](https://zh.wikipedia.org/wiki/阿隆佐·邱奇)**λ演算**,可接受函数作为输入参数和输出返回值。相对于指令式编程,函数式编程更加强调执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。 #### 1.1λ演算 **概念**:是一套从数学逻辑中发展,以变量绑定和替换的规则,来研究[函数](https://zh.wikipedia.org/wiki/函数)如何抽象化定义、函数如何被应用以及[递归](https://zh.wikipedia.org/wiki/递归)的[形式系统](https://zh.wikipedia.org/wiki/形式系統) **推演过程:** 定义一个符号λ,表示函数,类似数学f(x) 中的 f 函数,是一个抽象函数 定义标点. ,标点前的是参数,标点后的是返回值 f(x)=x 等价于 λ x . x f(x)=y 等价于 λ x . y ```java //布尔运算 逻辑可以想成二选一,而布尔值则表示为有两个参数的函数,它得到两个参数中的一个: //True 简称T t λ x y . x //FALSE 简称F f λ x y . y //逻辑运算 // And λ x y. x y F // OR λ x y. x T y // Not λ x. x F T //自然数 0 = λ f x. x 1 = λ f x. f x 2 = λ f x. f (f x) 3 = λ f x. f (f (f x)) ... N = λ f x. f^n x // 四则运算 ... ``` ### 2.实现 java中的实现目前就两种,匿名函数和lambda表达式 #### 2.1匿名函数 ```java public class Test1 { public static void main(String[] args) { String name = "小明"; Runnable runnable = new Runnable() { @Override public void run() { System.out.println(name); } }; Thread thread = new Thread(runnable); thread.start(); } } ``` 其实现原理,编译生成文件**Test1** 和**Test$1** ![image-20220908193307915](https://gitee.com/lifutian66/img/raw/master/img/image-20220908193307915.png) **Test$1** 就是内部内的类文件,**编译**期间生成 Test1.class ,使用javap 命令查看Test1.class,其文件内容如下,**invokespecial**指向实例化匿名内部类 **Test$1** ![image-20220908174612028](https://gitee.com/lifutian66/img/raw/master/img/image-20220908174612028.png) **缺点**:每一个匿名内部类在编译阶段都会生成 `主类名$数字` 内部类,使用时指向该引用,大量使用的话,导致子类型过多,jvm运行时调优困难 #### 2.2 lambda ```java public class Test2 { public static void main(String[] args) { String name = "小明"; Runnable runnable = () -> System.out.println(name); Thread thread = new Thread(runnable); thread.start(); } } ``` 只生成编译文件Test2.class ![image-20220908193211934](https://gitee.com/lifutian66/img/raw/master/img/image-20220908193211934.png) 使用javap 命令查看Test2.class,内部实现变成了如下图,最下面生成了InnerClasses ,并且生成引导/启动方法bootStrapMethods,LambdaMetafactory # metafactory返回 CallSite 动态调用点,又称为一种不可以更改链接对象的调用点。执行**invokedynamic**命令,对应的启动方法(bootstrap)会通过**ASM** 来生成一个适配器类(可通过 java -Djdk.internal.lambda.dumpProxyClasses 类,生成类文件 如下图),这个适配器类实现了对应的函数式接口,启动方法(bootstrap)会返回调用点 CallSite ,该调用点会链接到返回适配器类实例的方法句柄。有兴趣的可以去看看 java.lang.invok 方法句柄。 ![image-20220908201019884](https://gitee.com/lifutian66/img/raw/master/img/image-20220908201019884.png) ![image-20220910124423776](https://gitee.com/lifutian66/img/raw/master/img/image-20220910124423776.png) ### 3.使用 ##### 前言 **语法:** 符号单箭头 ->,表示参数和返回的区分, (a)->{ return a+1 } 单箭头 -> 前表示参数,参数用括号括起来,逗号分割,没有参数可以写成 (),当只有一个参数时,括号可以省略,a->{ return a+1 } 单箭头 -> 后表示返回,返回写成大括号 最后return结果, 也可以没有返回,返回也可以是简单的运算或者一句可写完的语句,这样就可以省略大括号。a-> a+1 双冒号(::)运算符在Java 8中被用作**方法引用(method reference)**语法: class/实例::方法,表示 (a,..)->a.方法(顺位参数) #### 3.1 stream 针对流的操作,从获取流,进行中间操作,最后将流消费,就像是工厂的流水线一样。 **特点:** 只能遍历一次,流只能消费一次!与迭代器类似,使用后,这个流就被消费掉,需要从原始数据再次获取流即可。 ##### 3.1.1获取流 1. 数值流 :三个原始类型的流 IntStream、DoubleStream和 LongStream ```java IntStream range = IntStream.range(1, 100); IntStream iterate = IntStream.iterate(1, n -> n + 1); IntStream generate = IntStream.generate(RandomUtil::randomInt); ``` 2. 普通对象流 : Stream ```java //集合 Stream longStream = new ArrayList().stream(); //自由组合 Stream strs = Stream.of("a", "b ", "c", "d"); //数组 Integer[] numbers = {2, 3, 5, 7, 11, 13}; Stream numbersStream = Arrays.stream(numbers); //文件流 Stream lines = Files.lines(Paths.get("路径"), Charset.defaultCharset()) //迭代 Stream iterate = Stream.iterate(0, n -> n + 2); //生成 随机 Stream generate = Stream.generate(Math::random); ``` ##### 3.1.2中间操作 ```java class A { String name; Integer age; List someBs; C c; } class B { String bName; Integer bAge; } class C { String cName; Integer cAge; } ``` 1. 映射 ```java List as = new ArrayList<>(); //将流中的每个数据 由类型A转成指定类型的返回 Stream bStream = as.stream().map(a -> { B b = new B(); b.bAge = a.age; b.bName = a.name; return b; }); //将流中的数据中的流取出,变成一个新的流 Stream bFlotStream = as.stream().flatMap(a -> a.someBs.stream()); ``` 2. 筛选 ```java // 将流中的每个数据都筛选,满足的留下,不满足的去掉 age>10的留下,否则筛掉 Stream aStream = as.stream().filter(x -> x.age != null && x.age > 10); ``` 3. 截短-跳过 ```java //跳过前2个数据 将流中的前3个留下 Stream limit = as.stream().skip(2).limit(3); ``` ##### 3.1.3消费流 消费掉获取的流,如需流式操作,需从源数据重新获取流。 1. 匹配 ```java //全部匹配 全部都不是null boolean isAllAgeNotNull = as.stream().allMatch(x -> x.age != null); //全部不匹配 全是空的 boolean isAllAgeNull = as.stream().noneMatch(x -> x.age != null); //至少匹配 有一个age不是null boolean isAnyAgeNotNull = as.stream().anyMatch(x -> x.age != null); //至少匹配 Optional anyNotNull = as.stream().filter(x -> x.age != null).findAny(); //第一个元素 age不是null的 第一个 Optional anyNotNullFirst = as.stream().filter(x -> x.age != null).findFirst(); ``` 2. 规约 ```java //统计个数 long count = as.stream().count(); //统计求和 int sum = IntStream.range(1, 100).sum(); //平均数 OptionalDouble average = IntStream.range(1, 100).average(); //最大值 OptionalInt max = IntStream.range(1, 100).max(); //最小值 OptionalInt min = IntStream.range(1, 100).min(); //自定义规则计算 int reduce = IntStream.range(1, 100).reduce(0, (a, b) -> a + b); ``` 3. 收集 收集器 collect,将流中数据收集起来,类java.util.stream.Collectors 为目前已经实现的收集器 ```java //supplier初始结果返回 accumulator累加器,将元素累加到结果集 combiner合并两个结果集 R collect(Supplier supplier,BiConsumer accumulator,BiConsumer combiner); //集合 List list = as.stream().collect(Collectors.toList()); List list1 = as.stream().collect((Supplier>)ArrayList::new, List::add, List::addAll); //set Set set = as.stream().collect(Collectors.toSet()); Set set1 = as.stream().collect((Supplier>) HashSet::new, Set::add, Set::addAll); //map Map> map = as.stream().collect(Collectors.toMap(a -> a.name, b -> b.someBs, (c, d) -> d)); //根据name分组 list Map> nameListMap = as.stream().collect(Collectors.groupingBy(a -> a.name)); //根据name分组 set Map> nameSet = as.stream().collect(Collectors.groupingBy(a -> a.name, Collectors.toSet())); //根据name分组 统计个数 Map nameCount = as.stream().collect(Collectors.groupingBy(a -> a.name, Collectors.counting())); //根据name分组 统计age求和 Map nameSumAge = as.stream().collect(Collectors.groupingBy(a -> a.name, Collectors.summingInt(b -> b.age))); //根据name、age 分组 Map>> nameAgeListMap = as.stream().collect(Collectors.groupingBy(a -> a.name, Collectors.groupingBy(b -> b.age))); ``` ##### 3.1.4实现 [RyzeUserName/java8-analysis: 浅析java8 (github.com)](https://github.com/RyzeUserName/java8-analysis) #### 3.2 optional 避免出现空指针,优化 if(obj!=null) 判断 ```java A a = new A(); // of 方法 a 必须不是null Optional notNullAOption = Optional.of(a); // of 方法 a Optional aOptional = Optional.ofNullable(a); // 调用方法 aOptional.map(x->x.someBs).map(y->y.size()); aOptional.map(x -> x.c).map(y -> y.d).map(z -> z.DName); // if(obj!=null) aOptional.ifPresent(x-> System.out.println(x.name)); // if(obj!=null) return new Object(); A a1 = aOptional.orElse(new A()); // if(obj!=null) throw new Exception(); aOptional.orElseThrow(Exception::new); // get 方法慎用,null会报错,尽量使用默认值的这种 A orElseGet = aOptional.orElseGet((Supplier) new A()); ``` #### 3.3 异步 ##### 3.3.1.parallelStream 只需要将stream 改为parallelStream 即可,适合非IO(网络传输)场景,使用也比较简单 ##### 3.3.2.CompletableFuture 类似Future,在将来的某刻返回结果的引用,缺点:Future结果之间的依赖性。 ```java public static String doSomething() { try { Thread.sleep(3); } catch (InterruptedException e) { throw new RuntimeException(e); } return RandomUtil.randomString(5); } public static String doSomethingTwo(String msg) { try { Thread.sleep(3); } catch (InterruptedException e) { throw new RuntimeException(e); } return RandomUtil.randomString(5) + msg; } public static String doOtherSomething() { try { Thread.sleep(3); } catch (InterruptedException e) { throw new RuntimeException(e); } return RandomUtil.randomString(5); } public static void main(String[] args) { CompletableFuture doSome = CompletableFuture.supplyAsync(Test2::doSomething); //对结果进行转化 CompletableFuture.supplyAsync(Test2::doSomething).thenApply(a -> a.split("")); //对结果就行操作 CompletableFuture.supplyAsync(Test2::doSomething).thenAccept(System.out::println); //异常处理 CompletableFuture.supplyAsync(Test2::doSomething).exceptionally(a->null); //需要前一个结果作为第二个的输入 CompletableFuture thenCompose = CompletableFuture.supplyAsync(Test2::doSomething).thenCompose(a -> CompletableFuture.supplyAsync(() -> doSomethingTwo(a))); //将两个异步操作的结果合并 CompletableFuture thenCombine = CompletableFuture.supplyAsync(Test2::doSomething).thenCombine(CompletableFuture.supplyAsync(Test2::doOtherSomething), String::join);} ``` ### 4.文献出处 1.维基百科 函数式编程 https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B 2.维基百科 λ演算https://zh.wikipedia.org/wiki/%CE%9B%E6%BC%94%E7%AE%97 3.深入理解Java虚拟机(第三版) 周志明 4.java8 实战(图灵)