本文为社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
Java 在最开端是不支撑函数式编程的,想来也好了解,因为在 Java 中类 Class 才是榜首等公民,这就导致在 Java 中完成编程不是件那么简单的事儿,不过尽管难,可是成果咱们也现已知道了,在 Java 8 这个大版本里为了支撑函数式编程,Java 引进了很多特重要特性,咱们在前面几篇文章中,分别学习了其中的 Lambda 表达式和 Stream API 里的各种流操作,今日这篇文章咱们再来整理一下 Java 内置给咱们供给的函数式接口。
本文纲要如下:
Java 依据常用需求场景的用例,笼统出了几个内置的函数式接口给开发者运用,比方Function
、 Supplier
等等,Stream 中各种操作办法的参数或者是回来值类型往往便是这些内置的函数式接口。
比方 Stream 中 map 操作办法的参数类型便是 Function
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
那为什么咱们在平时运用 Stream 操作的 map 办法时,从来没有见过声明这个类型的参数呢?咱们能够回忆一下咱们 Stream API 操作那一篇文章里运用 map 办法的比方,比方下面这个经过 map 办法把流中的每个元素转换成大写的比方。
List<String> list = new ArrayList<String>();
Stream<String> stream = list.stream();
Stream<String> streamMapped = stream.map((value) -> value.toUpperCase());
map 办法的参数直接是一个 Lambada 表达式:
(value) -> value.toUpperCase()
这个Lambda 表达式便是Function
接口的完成。
函数式接口的载体通常是 Lambda 表达式,经过 Lambda 表达式,编译器会依据 Lambda 表达式的参数和回来值推断出其完成的是哪个函数式接口。运用 Lambda 表达式完成接口,咱们不必像匿名内部类那样–指明类要完成的接口,所以像 Stream 操作中尽管参数或者回来值类型很多都是 Java 的内置函数式接口,可是咱们并没有显现的运用匿名类完成它们。
尽管Lambda 表达式运用起来很方便,不过这也从旁边面造成了咋一看到那些 Java 内置的函数式接口类型时,咱们会有点利诱“这货是啥?这货又是啥?”的感觉。
下面咱们先说一下函数式编程、Java 的函数式接口、Lambda 为什么只能完成函数式接口这几个问题,把这些东西搞清楚了再整理 Java 内置供给了哪些函数式接口。
函数式编程
函数式编程中包括以下两个要害的概念:
- 函数是榜首等公民
- 函数要满意一下束缚
- 函数的回来值仅取决于传递给函数的输入参数。
- 函数的履行没有副作用。
即便咱们在写程序的时分没有一直遵循所有这些规则,但仍然能够从运用函数式编程思维编写程序中获益良多。
接下来,咱们来看一下这两个要害概念再 Java 函数编程中的落地。
函数是一等公民
在函数式编程范式中,函数是语言中的榜首等公民。这意味着能够创立函数的“实例”,对函数实例的变量引用,就像对字符串、Map 或任何其他目标的引用相同。函数也能够作为参数传递给其他函数。
在 Java 中,函数明显不是榜首等公民,类才是。所以 Java 才引进 Lambda 表达式,这个语法糖从体现层上让 Java 具有了函数,让函数能够作为变量的引用、办法的参数等等。为啥说是从体现层呢?因为实际上在编译的时分 Java 编译器还是会把 Lambda 表达式编译成类。
纯函数
函数编程中,有个纯函数(Pure Function)的概念,假如一个函数满意以下条件,才是纯函数:
- 该函数的履行没有副作用。
- 函数的回来值仅取决于传递给函数的输入参数。
下面是一个 Java 中的纯函数(办法)示例
public class ObjectWithPureFunction{
public int sum(int a, int b) {
return a + b;
}
}
上面这个sum()
办法的回来值仅取决于其输入参数,而且sum()
是没有副作用的,它不会在任何当地修正函数之外的任何状况(变量)。
相反,这儿是一个非纯函数的比方:
public class ObjectWithNonPureFunction{
private int value = 0;
public int add(int nextValue) {
this.value += nextValue;
return this.value;
}
}
add()
办法运用成员变量value
来计算其回来值,而且它还修正了value
成员变量的状况,这代表它有副作用,这两个条件都导致add
办法不是一个纯函数
正如咱们看到的,函数式编程并不是解决所有问题的银弹。尤其是“函数是没有副作用的”这个准则就使得在一些场景下很难运用函数式编程,比方要写入数据库的场景,写入数据库就算是一个副作用。所以,咱们需求做的是了解函数式编程拿手解决哪些问题,把它用在正确的当地。
函数式接口
Java中的函数式接口在 Lambda 表达式那篇文章里提到过,这儿再具体说说。函数式接口是只有一个笼统办法的接口(笼统办法即未完成办法体的办法)。一个 Interface 接口中能够有多个办法,其中默许办法和静态办法都自带完成,可是只需接口中有且仅有一个办法没有被完成,那么这个接口就能够被看做是一个函数式接口。
下面这个接口只界说了一个笼统办法,明显它是一个函数式接口:
public interface MyInterface {
public void run();
}
下面这个接口中,界说了多个办法,不过它也是一个函数式接口:
public interface MyInterface2 {
public void run();
public default void doIt() {
System.out.println("doing it");
}
public static void doItStatically() {
System.out.println("doing it statically");
}
}
因为doIt
办法在接口中界说了默许完成,静态办法也有完成,接口中只有一个笼统办法run
没有供给完成,所以它满意函数式接口的要求。
这儿要注意,假如接口中有多个办法没有被完成,那么接口将不再是函数式接口,因此也就没办法用 Java 的 Lambda 表达式完成接口了。
编译器会依据 Lambda 表达式的参数和回来值类型推断出其完成的笼统办法,从而推断出其完成的接口,假如一个接口有多个笼统办法,明显是没办法用 Lambda 表达式完成该接口的。
@FunctionalInterface 注解
这儿扩充一个标示接口是函数式接口的注解@FunctionalInterface
@FunctionalInterface // 标明接口为函数式接口
public interface MyInterface {
public void run(); //笼统办法
}
一旦运用了该注解标示接口,Java 的编译器将会强制查看该接口是否满意函数式接口的要求:“的确有且仅有一个笼统办法”,否则将会报错。
需求注意的是,即便不运用该注解,只需一个接口满意函数式接口的要求,那它仍然是一个函数式接口,运用起来都相同。该注解只起到–标记接口指示编译器对其进行查看的作用。
Java 内置的函数式接口
Java 语言内置了一组为常见场景的用例设计的函数式接口,这样咱们就不必每次用到Lambda 表达式、Stream 操作时先创立函数式接口了,Java 的接口本身也支撑泛型类型,所以基本上 Java 内置的函数式接口就能满意咱们平时编程的需求,我自己在开发项目时,形象里很少见过有人自界说函数式接口。
在接下来的部分中,咱们具体介绍下 Java 内置为咱们供给了的函数式接口。
Function
Function
接口(全限定名:java.util.function.Function)是Java中最中心的函数式接口。 Function
接口表明一个承受单个参数并回来单个值的函数(办法)。以下是 Function 接口界说的:
@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));
}
default <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;
}
Function
接口本身只包括一个需求完成的笼统办法apply
,其他几个办法都已在接口中供给了完成,这正好契合上面咱们讲的函数式接口的界说:“有且仅有一个笼统办法的接口”。
Function 接口中的其他三个办法中compse
、andThen
这两个办法用于函数式编程的组合调用,identity
用于回来调用实体目标本身,咱们之前在把目标 List 转换为 Map 的内容中提到过,能够回看前面讲 List 的文章温习。
Function
接口用Java 的类这么完成
public class AddThree implements Function<Long, Long> {
@Override
public Long apply(Long aLong) {
return aLong + 3;
}
public static void main(String[] args) {
Function<Long, Long> adder = new AddThree();
Long result = adder.apply(4L);
System.out.println("result = " + result);
}
}
不过现实中没有这么用的,前面说过 Lambda 表达式是搭配函数式接口运用的,用Lambda表达式完成上Function 接口只需求一行,上面那个比方用 Lambda 完成的方式是:
Function<Long, Long> adder = (value) -> value + 3;
Long resultLambda = adder.apply(8L);
System.out.println("resultLambda = " + resultLambda);
是不是简洁了很多。后边的接口示例统一用 Lambda 表达式举例,不再用类完成占用太多篇幅。
Function
接口的常见应用是 Stream API 中的 map 操作办法,该办法的参数类型是Function
接口,表明参数是一个“接纳一个参数,并回来一个值的函数”。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
所以咱们在代码里常会见到这样运用 map 操作:
stream.map((value) -> value.toUpperCase())
Predicate
Predicate 接口 (全限定名:java.util.function.Predicate)表明一个接纳单个参数,并回来布尔值 true 或 false 的函数。以下是 Predicate 功能接口界说:
public interface Predicate<T> {
boolean test(T t);
}
Predicate 接口里还有几个供给了默许完成的办法,用于支撑函数组合等功能,这儿不再赘述。 用 Lambda 表达式完成 Predicate 接口的方式如下:
Predicate predicate = (value) -> value != null;
Stream API 中的 filter 过滤操作,接纳的便是一个完成了 Predicate 接口的参数。
Stream<T> filter(Predicate<? super T> predicate);
写代码时,会经常见到这样编写的 filter 操作:
Stream<String> longStringsStream = stream.filter((value) -> {
// 元素长度大于等于3,回来true,会被保留在 filter 发生的新流中。
return value.length() >= 3;
});
Supplier
Supplier 接口(java.util.function.Supplier),表明供给某种值的函数。其界说如下:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Supplier接口也能够被认为是工厂接口,它发生一个泛型成果。与 Function 不同的是,Supplier 不承受参数。
Supplier<Integer> supplier = () -> new Integer((int) (Math.random() * 1000D));
上面这个 Lambda 表达式的 Supplier 完成,用于回来一个新的 Integer 实例,其随机值介于 0 到 1000 之间。
Consume
Consumer 接口(java.util.function.Consume)表明一个函数,该函数接纳一个参数,可是不回来任何值。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Consumer 接口常用于表明:要在一个输入参数上履行的操作,比方下面这个用Lambda 表达式完成的 Consumer,它将作为参数传递给它的value
变量的值打印到System.out
标准输出中。
Consumer<Integer> consumer = (value) -> System.out.println(value);
Stream API 中的 forEach、peek 操作办法的参数便是 Consumer 接口类型的。
Stream<T> peek(Consumer<? super T> action);
void forEach(Consumer<? super T> action);
比方,Stream API 中的 forEach 操作,会像下面这样运用 Consume 接口的完成
Stream<String> stream = stringList.stream();
// 下面是Lambda 的简写方式
// 完整方式为:value -> System.out.println(value);
stream.forEach(System.out::println);
Optional
最终再介绍一下 Optional 接口,Optional 接口并不是一个函数式接口,这儿介绍它首要是因为它经常在一些 Stream 操作中出现,作为操作的回来值类型,所以趁着学习函数式编程的契机也学习一下它。
Optional 接口是预防NullPointerException
的好东西,它是一个简单的容器,其值能够是 null 或非 null。比方一个可能回来一个非空成果的办法,办法在有些情况下回来值,有些情况不满意回来条件回来空值,这种情况下运用 Optional 接口作为回来类型,比直接无值时回来 Null 要更安全。
接下来咱们看看 Optional 怎么运用:
// of 办法用于构建一个 Optional 容器
Optional<String> optional = Optional.of("bam");
// 判断值是否为空
optional.isPresent(); // true
// 取出值,假如不存在直接取会抛出反常
optional.get(); // "bam"
// 取值,值为空时回来 orElse 供给的默许值
optional.orElse("fallback"); // "bam"
// 假如只存在,履行ifPresent参数中指定的办法
optional.ifPresent((s) -> System.out.println(s.charAt(0)));// "b"
Stream 操作中像 findAny、 findFirst这样的操作办法都会回来一个 Optional 容器,意味着成果 Stream 可能为空,因此没有回来任何元素。咱们能够经过 Optional 的 isPresent() 办法查看是否找到了元素。
总结
本文从函数式编程思维、准则到 Java 对函数式编程的完成,给咱们整理了一遍,主张咱们重点了解 Java 的函数式接口,为什么 Lambda 表达式对函数式接口的完成,以及 Java 内置供给的覆盖了大多数函数式编程应用场景的函数式接口。
至此用 Java 编程那些绕不开的接口 这个子系列的文章现已更新完毕,感兴趣的请持续关注,后边还有更多实用、精彩的内容。
相关阅览
- Java Lambda 表达式的各种形状和运用场景,看这篇就够了
- Java Stream 的操作这么多,其实只有两大类,看完这篇就清晰了