写点什么

Java 如何支持函数式编程?

  • 2022 年 4 月 26 日
  • 本文字数:2815 字

    阅读完需:约 9 分钟

.max((o1,?o2)?->?o1-o2);?//?max 终止操作:返回 Optional<Integer>?


System.out.println(result.get());?//?输出 2?


}?


}?


Lambda 表达式


前面提到 Java 引入 Lambda 表达式的主要作用是简化代码编写。实际上,我们也可以不用 Lambda 表达式来书写例子中的代码。我们拿其中的 map 函数来举例说明。


下面三段代码,第一段代码展示了 map 函数的定义,实际上,map 函数接收的参数是一个 Function 接口,也就是函数接口。第二段代码展示了 map 函数的使用方式。第三段代码是针对第二段代码用 Lambda 表达式简化之后的写法。实际上,Lambda 表达式在 Java 中只是一个语法糖而已,底层是基于函数接口来实现的,也就是第二段代码展示的写法。


//?Stream 类中 map 函数的定义:?


public?interface?Stream<T>?extends?BaseStream<T,?Stream<T>>?{?


<R>?Stream<R>?map(Function<??super?T,???extends?R>?mapper);?


//...省略其他函数...?


}?


//?Stream 类中 map 的使用方法示例:?


Stream.of("fo",?"bar",?"hello").map(new?Function<String,?Integer>()?{?


@Override?


public?Integer?apply(String?s)?{?


return?s.length();?


}?


});?


//?用 Lambda 表达式简化后的写法:?


Stream.of("fo",?"bar",?"hello").map(s?->?s.length());?


Lambda 表达式包括三部分:输入、函数体、输出。表示出来的话就是下面这个样子:


(a,?b)?->?{?语句 1;语句 2;...;?return?输出;?}?//a,b 是输入参数?


实际上,Lambda 表达式的写法非常灵活。上面给出的是标准写法,还有很多简化写法。比如,如果输入参数只有一个,可以省略 (),直接写成 a->{…};如果没有入参,可以直接将输入和箭头都省略掉,只保留函数体;如果函数体只有一个语句,那可以将{}省略掉;如果函数没有返回值,return 语句就可以不用写了。


Optional<Integer>?result?=?Stream.of("f",?"ba",?"hello")?


.map(s?->?s.length())?


.filter(l?->?l?<=?3)?


.max((o1,?o2)?->?o1-o2);?


//?还原为函数接口的实现方式?


Optional<Integer>?result2?=?Stream.of("fo",?"bar",?"hello")?


.map(new?Function<String,?Integer>()?{?


@Override?


public?Integer?apply(String?s)?{?


return?s.length();?


}?


})?


.filter(new?Predicate<Integer>()?{?


@Override?


public?boolean?test(Integer?l)?{?


return?l?<=?3;?


}?


})?


.max(new?Comparator<Integer>()?{?


@Override?


public?int?compare(Integer?o1,?Integer?o2)?{?


return?o1?-?o2;?


}?


});?


Lambda 表达式与匿名类的异同集中体现在以下三点上:


  • Lambda 就是为了优化匿名内部类而生,Lambda 要比匿名类简洁的多得多。

  • Lambda 仅适用于函数式接口,匿名类不受限。

  • 即匿名类中的 this 是“匿名类对象”本身;Lambda 表达式中的 this 是指“调用 Lambda 表达式的对象”。


函数接口


实际上,上面一段代码中的 Function、Predicate、Comparator 都是函数接口。我们知道,C 语言支持函数指针,它可以把函数直接当变量来使用。


但是,Java 没有函数指针这样的语法。所以它通过函数接口,将函数包裹在接口中,当作变量来使用。实际上,函数接口就是接口。不过,它也有自己特别的地方,那就是要求只包含一个未实现的方法。因为只有这样,Lambda 表达式才能明确知道匹配的是哪个方法。如果有两个未实现的方法,并且接口入参、返回值都一样,那 Java 在翻译 Lambda 表达式的时候,就不知道表达式对应哪个方法了。


函数式接口也是 Java interface 的一种,但还需要满足:


  • 一个函数式接口只有一个抽象方法(single abstract method);

  • Object 类中的 public abstract method 不会被视为单一的抽象方法;

  • 函数式接口可以有默认方法和静态方法;

  • 函数式接口可以用 @FunctionalInterface 注解进行修饰。


满足这些条件的 interface,就可以被视为函数式接口。例如 Java 8 中的 Comparator 接口:


@FunctionalInterface?


public?interface?Comparator<T>?{?


/**?


*?single?abstract?method?


*?@since?1.8?


*/?


int?compare(T?o1,?T?o2);?


/**?


*?Object 类中的 public?abstract?method??


*?@since?1.8?


*/?


boolean?equals(Object?obj);?


/**?


*?默认方法?


*?@since?1.8?


*/?


default?Comparator<T>?reversed()?{?


return?Collections.reverseOrder(this);?


}?


/**?


*?静态方法?


*?@since?1.8?


*/?


public?static?<T?extends?Comparable<??super?T>>?Comparator<T>?reverseOrder()?{?


return?Collections.reverseOrder();?


}?


//省略...?


}?


函数式接口有什么用呢?一句话,函数式接口带给我们最大的好处就是:可以使用极简的 lambda 表达式实例化接口。为什么这么说呢?我们或多或少使用过一些只有一个抽象方法的接口,比如 Runnable、ActionListener、Comparator 等等,比如我们要用 Comparator 实现排序算法,我们的处理方式通常无外乎两种:


  • 规规矩矩的写一个实现了 Comparator 接口的 Java 类去封装排序逻辑。若业务需要多种排序方式,那就得写多个类提供多种实现,而这些实现往往只需使用一次。

  • 另外一种聪明一些的做法无外乎就是在需要的地方搞个匿名内部类,比如:


public?class?Test?{??


public?static?void?main(String?args[])?{??


List<Person>?persons?=?new?ArrayList<Person>();?


Collections.sort(persons,?new?Comparator<Person>(){?


@Override?


public?int?compare(Person?o1,?Person?o2)?{?


return?Integer.compareTo(o1.getAge(),?o2.getAge());?


}?


});?


}??


}?


匿名内部类实现的代码量没有多到哪里去,结构也还算清晰。Comparator 接口在 Jdk 1.8 的实现增加了 FunctionalInterface 注解,代表 Comparator 是一个函数式接口,使用者可放心的通过 lambda 表达式来实例化。那我们来看看使用 lambda 表达式来快速 new 一个自定义比较器所需要编写的代码:


Comparator<Person>?comparator?=?(p1,?p2)?->?Integer.compareTo(p1.getAge(),?p2.getAge());?


-> 前面的 () 是 Comparator 接口中 compare 方法的参数列表,-> 后面则是 compare 方法的方法体。


下面将 Java 提供的 Function、Predicate 这两个函数接口的源码,摘抄如下:


@FunctionalInterface?


public?interface?Function<T,?R>?{?


R?apply(T?t);??//?只有这一个未实现的方法?


default?<V>?Function<V,?R>?compose(Function<??super?V,???extends?T>?before)?{?


Objects.requireNonNull(before);?


return?(V?v)?->?apply(before.apply(v));?


}?


def 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 ault?<V>?Function<T,?V>?andThen(Function<??super?R,???extends?V>?after)?{?


Objects.requireNonNull(after);?


return?(T?t)?->?after.apply(apply(t));?


}?


static?<T>?Function<T,?T>?identity()?{?


return?t?->?t;?


}?


}?


@FunctionalInterface?


public?interface?Predicate<T>?{?


boolean?test(T?t);?//?只有这一个未实现的方法?


default?Predicate<T>?and(Predicate<??super?T>?other)?{?


Objects.requireNonNull(other);?


return?(t)?->?test(t)?&&?other.test(t);?


}?


default?Predicate<T>?negate()?{?


return?(t)?->?!test(t);?


}?


default?Predicate<T>?or(Predicate<??super?T>?other)?{?

用户头像

还未添加个人签名 2022.04.13 加入

还未添加个人简介

评论

发布
暂无评论
Java如何支持函数式编程?_Java_爱好编程进阶_InfoQ写作社区