Bootstrap

【Java从入门到放弃 之 函数式编程】

函数式编程

首先我们大家见过不少函数这个概念了。尤其是大学基础课C语言的时候,我们就学到了函数,学到了面向过程编程。那什么是函数式编程呢?函数式编程是一种编程范式,它将计算看作是数学函数的求值过程。函数式编程的核心思想是将计算过程分解为一系列的函数调用,而不是通过改变状态来实现计算。函数式编程强调函数的纯粹性和不可变性,函数的返回值只取决于输入参数,不会受外部状态的影响,并且函数执行过程中不会对外部状态产生副作用。函数式编程还提倡使用高阶函数,即函数可以作为参数传递给其他函数,或者作为返回值返回给其他函数。这种编程范式可以使代码更加简洁、模块化和可复用,同时有助于并发编程和并行计算。函数式编程在编程语言中有一些特定的实现,如Haskell、Lisp和Clojure等。
这个看起来是不是脑袋都大了,我也看的有点迷了,我的理解就是函数式编程就是,允许我们传入一个函数本身,经过处理之后,返回一个函数。
因为函数式编程最早是阿隆佐·邱奇研究的函数变换,又称lambda λ变换。 Java自Java8开始支持函数式编程。

Lambda表达式

  1. 通过接口传递代码
    之前我们学习sort算法的时候,我们对一些自定义对象进行排序的时候,我们需要把排序的规则传递给sort,这个时候我们就是通过匿名内部类进行传递,实际上就是传递的比较的代码
        Arrays.sort(files, new Comparator<File>() {
            @Override
            public int compare(File f1, File f2) {
                return f1.getName().compareTo(f2.getName());
            }
        });
  1. Lambda语法
    Java8 提供了Lambda表达式,对于上面的代码,我们改用Lambda写,如下
        Arrays.sort(files, (f, f1) -> {
            return f.getName().compareTo(f1.getName());
        });

我们可以看到使用Lambda表达式可以简化代码,参数(f, f1) 的类型我们都不用写,为什么? 因为这个地方接受的是一个Comparator接口这个接口,编译器可以自动推断出是String类型。-> { } 这个地方写方法体。

  1. FunctionalInterface
    我们把只定义单个方法的接口称为FunctionalInterface,用@FunctionalInterface注解标记
package java.lang;

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

常见的function

Java 8定义了大量的预定义函数式接口,用于常见类型的代码传递,这些函数定义在包java.util.function下面。
function

** 代码演示**
我们展示一下function下面的一些接口的使用,方便大家理解。

public class Test21 {

    public static void main(String[] args) {
        List<Dog> dogs = Arrays.asList(new Dog[]{new Dog(1, "wangcai"), new Dog(2, "dahuang")});
        dogs = filter(dogs, dog -> dog.age < 2);
        System.out.println(dogs.size());
        dogs = filter(dogs, dog -> dog.name.length() > 6);
        System.out.println(dogs.size());
    }

    public static <E> List<E> filter(List<E> list, Predicate<E> predicate) {
        List<E> ret = new ArrayList<>();
        for (E e : list) {
            if (predicate.test(e)) {
                ret.add(e);
            }
        }
        return ret;
    }
}

class Dog{
    public int age;
    public String name;

    public Dog(int age, String name) {
        this.age = age;
        this.name = name;
    }
}

我们简单讲一下这段代码,其实我们关注的核心应该在通过使用function传递代码,这样我们写出了一个通用的方法。这样的话我们的filter可以灵活的传入多个过滤条件。

使用Stream

针对常见的集合数据处理,Java8引入了Stream的概念。Stream用于存储顺序输出的Java对象,用于我们的计算或者业务。那它跟我们的容器由什么区别,为啥要引入这个东西呢?
Stream是实时计算出来的,里面的数据可能未分配内存。(要注意我们Lambda表达式处理的是函数到函数的变换,如果我们现在要处理的是有理数集合,我们简单的之前的容器就承载不了,就比较适合使用容器)

如何创建Stream

public class Test22 {
    public static void main(String[] args) {
        // 直接基于Stream.of
        Stream<Integer> stream = Stream.of(1, 2, 3, 4);

        // 利用容器
        Arrays.stream(new Integer[]{1, 2, 3, 4});

        // 基于Supplier
        Stream.generate(MySupplier::new);
    }
}

class MySupplier implements Supplier<Integer> {

    int count = 0;
    @Override
    public Integer get() {
        return count++;
    }
}

代码都写好了注释,看注释就可以了。

中间操作

Stream API中间操作有filter和map还有distinct、sorted、skip、limit、peek、mapToLong、mapToInt、mapToDouble、flatMap等

  1. distinct 用来去重的
  2. sorted 用来排序
  3. skip 跳过
  4. limit 限定多个
  5. peek 返回元素
  6. mapToLong、mapToInt、mapToDouble 对数据进行映射处理
  7. flatMap 简单理解就是1-n的映射

终端操作

Stream API的终端操作有collect、max、min、count、allMatch、anyMatch、noneMatch、findFirst、findAny、forEach、toArray、reduce等

  1. collect 收集
  2. max/min 最大最小
  3. allMatch 所有匹配的
  4. anyMatch 任何一个匹配的
  5. noneMatch 不匹配的
  6. findFirst 找到第一个
  7. findAny 找到任意一个
  8. forEach 循环
  9. toArray 转换成数组
;