天天看点

面试官问:说说你对Java函数式编程的理解

常见的面试问题

总结一下,在java程序员的面试中,经常会被问到类似这样的问题:

java中的函数式接口是什么意思?

注解 @functionalinterface 的作用是什么?

实现一个函数式接口有哪几种方式?

lambda表达式和匿名内部类有什么区别?

java中的方法引用有哪几种形式?

能说说你对 stream 接口中的 map 和 reduce 方法的理解吗?

stream并行编程的底层实现用了什么多线程框架?

能说说 stream 并行编程的适用场景以及注意事项吗?

concurrenthashmap中,有哪些方法具备原子性?

为什么在lambda表达式中引用外部变量时,要求外部变量是final的?怎么绕开这个限制?

问题答案

只有一个抽象方法的接口都属于函数式接口。英文术语为 functional interface 。

@functionalinterface 主要是告诉编译器它修饰的接口是一个函数式接口,如果接口的定义不符合函数式接口的规范,那么在编译阶段就会报错。当然,我们也可以不加这个注解,对代码的使用没有任何影响。

有3种方式:

通过一个类来实现,包括常规的类和匿名内部类

通过lambda表达式来实现

通过方法引用来实现

匿名内部类本质是一个类,只是不需要程序员显示指定类名,编译器会自动为该类取名。而 lambda 表达式本质是一个函数,当然,编译器也会为它取名。在jvm层面,匿名内部类对应的是一个 class 文件,而 lambda 表达式对应的是它所在主类的一个私有方法。

有4种形式:

object::instancemethod 对象 + 实例方法

classname::staticmethod 类名 + 静态方法

classname::new 类名 + new关键字,构造方法引用

classname::instancemethod 类名 + 实例方法

map方法的作用是遍历stream中的每个元素,将每个元素映射为另一个元素。map,来源于数学中的概念函数映射。

reduce方法的作用是使用指定的计算逻辑,将多个元素逐个计算处理,最终得到一个结果。简单来说,reduce的过程就是将多个元素转换为一个最终结果。典型的包括将多个数字累加得到一个和,或者累乘得到一个积,或者将多个元素汇总起来得到一个arraylist。

stream并行编程的底层实现用了什么多线程技术?

使用了 forkjoinpool 技术。forkjoinpool是java 7引入的用于并行执行的任务框架,核心思想是将一个大任务拆分成多个小任务(即fork),然后再将多个小任务的处理结果汇总到一个结果上(即join)。此外,它也提供基本的线程池功能,譬如设置最大并发线程数,关闭线程池等。

能说说 stream 并行编程的优点和局限性吗?

一般来说,在web应用中不推荐使用 stream 的并行编程接口,因为 stream 并行编程的底层是基于 forkjoinpool ,而 forkjoinpool 的工作线程数是在虚拟机启动时指定的,如果 stream 并行执行的任务数量过多或耗时过多,甚至会影响应用程序中其它使用 forkjoinpool 的功能。

但如果是在某些任务单一、能确保不影响其它任务的场景中,使用 stream 并行编程能带来编码上的便利性:即使数据源不是线程安全的,通过 stream 并行编程,也能轻松写出多线程并行处理任务的代码,不需要考虑加锁。

有4个方法具备原子性:

compute

computeifabsent

computeifpresent

putifabsent

原子性的含义是说:从“判断 concurrenthashmap 是否存在指定的key”开始,然后“计算对应的value”,最后“向 concurrenthashmap 写入value”,这一系列的操作是一个原子性的过程 —— 就像加了锁一样,整个过程不会被别的线程打断。

在lambda表达式中引用外部变量,会形成一个闭包,在多线程环境下,容易导致线程安全问题,防不胜防。因此,java规定了,在lambda表达式内部引用外部变量的话,必须是final的,即不可变对象,只能赋值一次,不可修改。

但是,我们可以通过将该外部变量声明为一个数组或一个类(包括容器类)就可以修改其中的值。