面试官问:说说你对Java函数式编程的理解
常见的面试问题
总结一下,在Java程序员的面试中,经常会被问到类似这样的问题:
- Java中的函数式接口是什么意思?
- 注解 @FunctionalInterface 的作用是什么?
- 实现一个函数式接口有哪几种方式?
- lambda表达式和匿名内部类有什么区别?
- Java中的方法引用有哪几种形式?
- 能说说你对 Stream 接口中的 map 和 reduce 方法的理解吗?
- Stream并行编程的底层实现用了什么多线程框架?
- 能说说 Stream 并行编程的适用场景以及注意事项吗?
- ConcurrentHashMap中,有哪些方法具备原子性?
- 为什么在lambda表达式中引用外部变量时,要求外部变量是final的?怎么绕开这个限制?
问题答案
- Java中的函数式接口是什么意思?
只有一个抽象方法的接口都属于函数式接口。英文术语为 Functional Interface 。
- 注解 @FunctionalInterface 的作用是什么?
@FunctionalInterface 主要是告诉编译器它修饰的接口是一个函数式接口,如果接口的定义不符合函数式接口的规范,那么在编译阶段就会报错。当然,我们也可以不加这个注解,对代码的使用没有任何影响。
- 实现一个函数式接口有哪几种方式?
有3种方式:
- 通过一个类来实现,包括常规的类和匿名内部类
- 通过lambda表达式来实现
- 通过方法引用来实现
- lambda表达式和匿名内部类有什么区别?
匿名内部类本质是一个类,只是不需要程序员显示指定类名,编译器会自动为该类取名。而 lambda 表达式本质是一个函数,当然,编译器也会为它取名。在JVM层面,匿名内部类对应的是一个 class 文件,而 lambda 表达式对应的是它所在主类的一个私有方法。
- Java中的方法引用有哪几种形式?
有4种形式:
- object::instanceMethod 对象 + 实例方法
- ClassName::staticMethod 类名 + 静态方法
- ClassName::new 类名 + new关键字,构造方法引用
- ClassName::instanceMethod 类名 + 实例方法
- 能说说你对 Stream 接口中的 map 和 reduce 方法的理解吗?
map方法的作用是遍历Stream中的每个元素,将每个元素映射为另一个元素。map,来源于数学中的概念函数映射。
reduce方法的作用是使用指定的计算逻辑,将多个元素逐个计算处理,最终得到一个结果。简单来说,reduce的过程就是将多个元素转换为一个最终结果。典型的包括将多个数字累加得到一个和,或者累乘得到一个积,或者将多个元素汇总起来得到一个ArrayList。
- Stream并行编程的底层实现用了什么多线程技术?
使用了 ForkJoinPool 技术。ForkJoinPool是Java 7引入的用于并行执行的任务框架,核心思想是将一个大任务拆分成多个小任务(即fork),然后再将多个小任务的处理结果汇总到一个结果上(即join)。此外,它也提供基本的线程池功能,譬如设置最大并发线程数,关闭线程池等。
- 能说说 Stream 并行编程的优点和局限性吗?
一般来说,在web应用中不推荐使用 Stream 的并行编程接口,因为 Stream 并行编程的底层是基于 ForkJoinPool ,而 ForkJoinPool 的工作线程数是在虚拟机启动时指定的,如果 Stream 并行执行的任务数量过多或耗时过多,甚至会影响应用程序中其它使用 ForkJoinPool 的功能。
但如果是在某些任务单一、能确保不影响其它任务的场景中,使用 Stream 并行编程能带来编码上的便利性:即使数据源不是线程安全的,通过 Stream 并行编程,也能轻松写出多线程并行处理任务的代码,不需要考虑加锁。
- ConcurrentHashMap中,有哪些方法具备原子性?
有4个方法具备原子性:
- compute
- computeIfAbsent
- computeIfPresent
- putIfAbsent
原子性的含义是说:从“判断 ConcurrentHashMap 是否存在指定的key”开始,然后“计算对应的value”,最后“向 ConcurrentHashMap 写入value”,这一系列的操作是一个原子性的过程 —— 就像加了锁一样,整个过程不会被别的线程打断。
- 为什么在lambda表达式中引用外部变量时,要求外部变量是final的?怎么绕开这个限制?
在lambda表达式中引用外部变量,会形成一个闭包,在多线程环境下,容易导致线程安全问题,防不胜防。因此,Java规定了,在lambda表达式内部引用外部变量的话,必须是final的,即不可变对象,只能赋值一次,不可修改。
但是,我们可以通过将该外部变量声明为一个数组或一个类(包括容器类)就可以修改其中的值。