java 函数式编程

220 阅读4分钟

java在java 8以后加入了函数化编程,函数式编程的优点在提高编码的效率,增强代码的可读性。而在文中我们首先讨论一下java如何实现函数化编程以及函数化编程和基本的面向对象的实现具体有什么不同和相同的地方。

lambda表达式

可以理解为简洁的匿名函数, 语法:参数->具体的实现 函数描述符:函数式接口的方法 参数和实现必须和函数描述符的参数和返回值对应 可以使用局部变量,但必须是final类型,或事实上是不可变的类型。

方法引用

stream 流的应用

stream流出现的场景: 在jdk 1.8的编写期间,google发布了举世皆知的三大论文,改变了整个编程世界。其中,大数据,流式处理,分布式系统等等概念影响到了所有人。 stream 流就是在这样的一个场景出现的,

采用 iterator 迭代器 举例

List<String> example = Arrays.asList(new String[]{"A", "B", "C"});
// 遍历List,把大写转成小写
Iterator<String> iterator = example.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next().toLowerCase());
}

采用stream 内部迭代 举例

List<String> example2 = Arrays.asList(new String[]{"A", "B", "C"});
example2.stream().forEach(e -> System.out.println(e.toLowerCase()));

两种方式的实现最后结果是一样的,不一样的是过程中,Stream 帮助我们实现了先前我们自己实现迭代器的部分,让我们把迭代的方法也交给了jvm的部分。第一个好处就是在于性能的优化,数据越大,lambdra的方法就可以更多的优化整个迭代计算的方法。

映射 :

flatMap和 map的实现方式以及区别,大体来说:

map: 对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong和mapToDouble。这三个方法也比较好理解,比如mapToInt就是把原始Stream转换成一个新的Stream,这个新生成的Stream中的元素都是int类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗;

图示:

image.png

flatMap:和map类似,不同的是其每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中

图示:

image.png

举例实现:

比如一个List<?>,map能够直接操作list中的每个对象

List<Integer> integers = new ArrayList<>();
//添加数据略
integers.stream.map(i -> i + 1).foreach(System.out::println);

而使用flatmap使我们能够操作更深一层的数据,如下: List<List<?>>

List<List<Integer>> outer = new ArrayList<>();
List<Integer> inner1 = new ArrayList<>();
inner1.add(1);
List<Integer> inner2 = new ArrayList<>();
inner1.add(2);
List<Integer> inner3 = new ArrayList<>();
inner1.add(3);
List<Integer> inner4 = new ArrayList<>();
inner1.add(4);
List<Integer> inner5 = new ArrayList<>();
inner1.add(5);
outer.add(inner1);
outer.add(inner2);
outer.add(inner3);
outer.add(inner4);
outer.add(inner5);
List<Integer> result = outer.stream().flatMap(inner -> inner.stream().map(i -> i + 1)).collect(toList());
System.out.println(result);

运行的结果都是

屏幕快照 2021-12-28 下午8.52.30.png

但是flatMap的操作比起map更深了一层,两种方法都有其使用的不同业务场景

查找和匹配 :

mapReduce归约方法

optional

函数式编程

函数可以作为参数传递,也可以作为返回值返回,无副作用(不去改变原有的数据结构,stream方法是复制一个新的副本然后返回流,对原有的部分没有发生改变),只通过参数和返回值同外部交互。会读取外部的信息,但是原则上不会修改外部的信息。

Function Interface

@FunctionInterface 注解是java8的一个新注解, 可以使用lambda表达式来表示该接口的实现

通过JDK8源码javadoc,可以知道这个注解有以下特点:

1、该注解只能标记在"有且仅有一个抽象方法"的接口上。

2、JDK8接口中的静态方法和默认方法,都不算是抽象方法。

3、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。

4、该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。

@FunctionalInterface
public interface Runnable {
  public abstract void run();
}

java 8给不少常用的接口加上了这个实现,比如runnable接口,所以java 8以后 runnable就可以用lambda来实现了,

// Java 8之前:
new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println("Before Java8");
    }
}).start();


// Java 8之后:
new Thread( () -> System.out.println("In Java8, Lambda expression") ).start();

这个注解使得不少接口的调用方式大大的简洁了

接口的默认方法:

接口在java8 之前只能有抽象方法,java代码的特性是单继承,多实现, 使用default关键字使得接口也能够拥有默认方法,从而达到了c++等语言的多继承的同样的效果,这是对java语言的一种补充,因为上文提到了静态方法和默认方法,都不算是抽象方法。所以函数式接口也还是可以拥有多个默认的default方法的。

// 正确的函数式接口
@FunctionalInterface
public interface TestInterface {
 
    
    // 抽象方法
    public void sub();
 
    // java.lang.Object中的方法不是抽象方法
    public boolean equals(Object var1);
 
    // default不是抽象方法
    public default void defaultMethod(){
 
    }
 
    // static不是抽象方法
    public static void staticMethod(){
 
    }
}