• Java函数式编程原理以及应用


    一. 函数式编程

    Java8所有的新特性基本基于函数式编程的思想,函数式编程的带来,给Java注入了新鲜的活力。

    下面来近距离观察一下函数式编程的几个特点:

    • 函数可以作为变量、参数、返回值和数据类型。
    • 基于表达式来替代方法的调用
    • 函数无状态,可以并发和独立使用
    • 函数无副作用,不会修改外部的变量
    • 函数结果确定性;同样的输入,必然会有同样的结果。

    下面jdk1.8里面对函数式编程的定义。只是一个  FunctionalInterface 接口特别的简单。

    1 @Documented
    2 @Retention(RetentionPolicy.RUNTIME)
    3 @Target(ElementType.TYPE)
    4 public @interface FunctionalInterface {}

    这个函数式接口有几点以下的限制:

    • 唯一的抽象方法,有且仅有一个 (即所有的函数式接口,有且只能有一个抽象方法)
    • 加上标注,则会触发JavaCompiler的检查。对于符合函数接口的接口,加不加都无关紧要,但是加上则会提供一层编译检查的保障。如果不符合,则会报错。 
    • 不能被覆盖之后,再声明为抽象方法,则不算抽象方法。例如接口实现了Object中的方法。 
    • 可用于lambda类型的使用方式 

    二. Java8新增函数式接口

    Stream的操作是建立在函数式接口的组合之上的。Java8中新增的函数式接口都在java.util.function包下。这些函数式接口可以有多种分类方式。

    2.1 Function

    Function是从T到R的一元映射函数。将参数T传递给一个函数,返回R。即R = Function(T)

    Function最常用的应该是  <R> Stream<R> map(Function<? super T, ? extends R> mapper);

    比如List<Person> person里面有age,name.... 我传入age,他就会返回age的集合给我。

     1 @FunctionalInterface
     2 public interface Function<T, R> {
     3 
     4     R apply(T t);
     5 
     6     default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
     7         Objects.requireNonNull(before);
     8         return (V v) -> apply(before.apply(v));
     9     }
    10 
    11     default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    12         Objects.requireNonNull(after);
    13         return (T t) -> after.apply(apply(t));
    14     }
    15 
    16     static <T> Function<T, T> identity() {
    17         return t -> t;
    18     }
    19 }

    2.2 Predicate

    Predicate是一个谓词函数,主要作为一个谓词演算推导真假值存在,返回布尔值的函数。Predicate等价于一个Function的boolean型返回值的子集。

    predicate最常用的莫过于  Stream<T> filter(Predicate<? super T> predicate);  

    比如我要过滤年龄 > 18 的人,我传入age,判断是否为true。为true则保留,false丢弃。

     1 @FunctionalInterface
     2 public interface Predicate<T> {
     3 
     4     boolean test(T t);
     5 
     6     default Predicate<T> and(Predicate<? super T> other) {
     7         Objects.requireNonNull(other);
     8         return (t) -> test(t) && other.test(t);
     9     }
    10 
    11     default Predicate<T> negate() {
    12         return (t) -> !test(t);
    13     }
    14 
    15     default Predicate<T> or(Predicate<? super T> other) {
    16         Objects.requireNonNull(other);
    17         return (t) -> test(t) || other.test(t);
    18     }
    19 
    20     static <T> Predicate<T> isEqual(Object targetRef) {
    21         return (null == targetRef)
    22                 ? Objects::isNull
    23                 : object -> targetRef.equals(object);
    24     }
    25 }

    2.3 Consumer

    Consumer是从T到void的一元函数,接受一个入参但不返回任何结果的操作。

    Consumer最常用的肯定是   default void forEach(Consumer<? super T> action) {}

    这是一段forEach循环的代码,传入实现的方法,并不返回任何值。只是循环。

     1 @FunctionalInterface
     2 public interface Consumer<T> {
     3 
     4     void accept(T t);
     5 
     6     default Consumer<T> andThen(Consumer<? super T> after) {
     7         Objects.requireNonNull(after);
     8         return (T t) -> { accept(t); after.accept(t); };
     9     }
    10 }

    三. Lambda表达式

    3.1 基本语法

    Lambda 的基本结构为 (arguments) -> body,有如下几种情况:

    • 参数类型可推导时,不需要指定类型,如 (a) -> System.out.println(a)
    • 当只有一个参数且类型可推导时,不强制写 (), 如 a -> System.out.println(a)
    • 参数指定类型时,必须有括号,如 (int a) -> System.out.println(a)
    • 参数可以为空,如 () -> System.out.println(“hello”)
    • body 需要用 {} 包含语句,当只有一条语句时 {} 可省略

    3.2 Lambda原理

    比如如下代码:

     1 List<Integer> list = new ArrayList<>();
     2 
     3 list.stream().filter((x) -> x >= 18)
     4 
     5 Stream<T> filter(Predicate<? super T> predicate);
     6 
     7 @FunctionalInterface
     8 public interface Predicate<T> {
     9 
    10     boolean test(T t);
    11 
    12 }

    比如List里面存个个人的年龄,现在筛选出年龄大于等于18的人。

    此时我们就可以用  list.stream().filter((x) -> x >= 18)   这就是一个典型的lambda表达式

    (x) -> x >= 18 传给  Predicate 函数式接口。

    原理其实是:

    JVM帮我们动态生成了一个内部类,然后这个内部类实现了 Predicate 这个函数式接口。

    重写了里面的test方法。生成的类似如下:

    1 static final class Main$$Lambda$1 implements Predicate<Integer> {
    2     private Main$$Lambda$1() {
    3     }
    4 
    5     @Override
    6     public boolean test(Integer x) {
    7         return x >= 18;
    8     }
    9 }

    3.3 Lambda用法

     1 public class Main {
     2     public static void main(String[] args) {
     3 
     4         List<Integer> list = new ArrayList<>();
     5         list.add(40);
     6         list.add(50);
     7         list.add(20);
     8         list.add(30);
     9         List<Integer> collect = list.stream().filter(x -> x >= 30)
    10                 .map((x) -> x + 10).sorted((x, y) -> -x.compareTo(y))
    11                 .collect(Collectors.toList());
    12         System.out.println(collect);
    13     }
    14 }

    这个一段很典型的Lambda + Stream的用法。

    • list.stream()获取list的stream的流
    • filter筛选出年龄大于30的人 (里面是一个Predicate接口,返回真假)
    • map做一个function映射
    • sort排序,里面是compartor

    四. 总结

    Lambda 表达式可以减少很多代码,能提高生产力。但也要理解其原理。比如3.3中的代码,为什么filter里面是断言表达式,map里面是function表达式。

    这都要从lambda的原理入手,也就是JVM动态生成一个内部类,并继承其中的抽象方法。

    本次主要介绍了Java函数式编程的原理以及应用,主要从Stream和lambda入手。通过一些简单的概念,以及代码,更好的理解Java的函数式编程。

    掌握Java的函数式编程,对平时我们开发代码,看其他人的代码,都有很大的帮助。

    且行且珍惜,加油!

  • 相关阅读:
    5.16
    4.29
    二十节 作业
    作业
    作业
    控件
    选择排序
    百钱买百鸡
    用颜色变色变换来调整图像的对比度和亮度
    图像的裁剪
  • 原文地址:https://www.cnblogs.com/wenbochang/p/11385491.html
Copyright © 2020-2023  润新知