Java--浅析函数式接口&Lambda表达式&方法引用&Stream API

865 阅读5分钟

公司的项目中有大量使用 Lambda 以及 Stream API 这种 Java 8 的新特性,遂决定对此进行一番深入的探究。关于 Lambda 表达式的使用我在这里不再赘述,本篇博客主要讲解 Lambda 表达式的实现原理以及 Stream API 的简单使用。

1 函数式接口

理解函数式接口,有助于你更好的理解 Lambda 表达式以及 Stream API 的使用。

@FunctionalInterface
public interface GreetingService {

    void sayMessage(String message);
}

如上面代码所示,就是一个函数式接口,一个 interface,里面只有一个抽象方法,其他什么都没有。

简单总结一下函数式接口的特征:

  • FunctionalInterface 注解标注一个函数式接口,不能标注类,方法,枚举,属性这些。
  • 如果接口被标注了 @FunctionalInterface,这个类就必须符合函数式接口的规范。
  • 即使一个接口没有标注 @FunctionalInterface,如果这个接口满足函数式接口规则,依旧可以被当作函数式接口。

注意:interface 中重写 Object 类中的抽象方法,不会增加接口的方法数,因为接口的实现类都是 Object 的子类。

说完函数式接口,我们就可以说一下神奇的 Lambda 表达式了。

2 Lambda表达式

Java 中的 Lambda 表达式有三种形式,我们来举 3 个例子:

// 1.
() -> System.out.println("Hello Lambda");

// 2.
(number1, number2) -> int a = number1 + number2;

// 3.
(number1, number2) -> {
		int a = number1 + number2;
		System.out.println(a);
}

大致形式就是 (param1, param2, param3, param4…) -> { doing…… };

再说回函数式接口,事实上,Lambda 表达式就是函数式接口的一个实现。我们在简单定义一个线程的实现的时候,可以这样写:Runnable thread = () -> System.out.println("hello world");

很典型的一个 Lambda 表达式,定义一个线程,这个线程做的事情是打印 ”hello world”。可以看到,() -> System.out.println("hello world"); 实际上就是 run() 的实现。Runnable 的源码如下:

@FunctionalInterface
public interface Runnable {

    public abstract void run();
}

我想,你现在应该已经能够理解 Lambda 表达式与函数式接口之间的关系了。

3 方法引用

如果你学过 C 语言或者 C++ 的话,应该知道有一个叫做函数指针(是一个指针,指向函数)的东西。Java 中的方法引用实际上想要表达的意思也是引用一个方法的实现,可以理解为调用了一个指针,这个指针指向了你所引用的方法,但 Java 中是没有指针这一概念的,这里只是做了一个类比。

方法引用与函数式接口也脱不了干系,但他的作用乃是为了让 Lambda 表达式变的更加简洁、易懂。举个栗子:

List<Integer> list = Arrays.asList(1, 2, 3, 4);
// 关于stream表达式我们之后再说
List<String> strList = list.stream().map(String::valueOf).collect(Collectors.toList());

重点来看 String::valueOf 这个东西,从字面上的意思来看,它好像是调用了 String 类的 valueOf 方法。为什么可以这样去写呢,我们来看一下 stream 中 map 方法的定义:<R> Stream<R> map(Function<? super T, ? extends R> mapper);,发现 map 的入参是 Function<? super T, ? extends R> mapper,再继续看 Function 类型的实现:

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

		... ...
}

看到这里就明白了,map 的入参还是一个函数式接口,而这个函数式接口中的抽象方法本来同样需要通过编写 Lambda 表达式给出实现,但有了方法引用之后,我们直接可以通过 String::valueOf 这种形式,将 String 类中的 valueOf 绑定到 map 所需的函数式接口中,stream 流中的每一个元素都会将自己作为入参调用 String 类的 valueOf 方法,而不用我们再去编写具有相同功能的 Lambda 表达式了。

方法引用总共分四类:类名::静态方法名、对象名::实例方法名、类名::实例方法名、类名::new,Java 8 中还新增了几类常用的函数式接口,具体的我不再讲述,有兴趣的可以阅读 Java8 Lambda表达式、函数式接口、方法引用 这一篇文章。

4 Stream API

Stream API 同样以函数式接口为实现基础。这里的 Stream(流)和 I/O 流可不一样,这里的流主要用于 Java 集合,流式编程作为 Java 8 的亮点之一,是继 Java 5 之后对集合的再一次升级,可以说 Java 8 几大特性中,Stream API 是作为Java 函数式的主角来设计的,夸张的说,有了 Stream API 之后,万物皆可一行代码。

使用 Stream 操作集合的示意图大概可以表示如下:

首先通过 source 产生流,然后依次通过一些中间操作,比如过滤,转换,限制等,最后结束对流的操 作。Stream 也可以理解为一个更加高级的迭代器,主要的作用便是在遍历其中每一个元素的同时加以某种操作,最后再通过 collect 接口进行收集以完成对集合的整体操作。

这样说也许太过抽象,我们以上一节的代码为例:List<String> strList = list.stream().map(String::valueOf).collect(Collectors.toList());,实际上这一串代码代表的就是对 list 集合中的每一个元素进行迭代,每个元素都被转换为字符串,最终再以集合的形式返回。如果这一过程不使用 Stream API 而使用 for 循环的话,代码长度肯定不止一行。使用 Stream API 后,代码变的更加简洁与易读。更多 Stream API 的使用方法大家可以自行搜阅相关资料,我就不再赘述了。

Stream 的强大不仅体现在这里,使用 Stream API 可以极为方便与快捷的写出简单易读的高并发程序,博主目前对这块也不太熟悉,因此就不再展开了~

5 总结

函数式接口乃是理解 Lambda 表达式以及 Stream API 的关键所在;

善用 Lambda 表达式、Stream API、方法引用,写代码真的很爽。

6 参考阅读

函数式接口和Lambda表达式深入理解

Java 8 Lambda表达式、函数式接口、方法引用

Java 8 Streams API 详解