面試官問:說說你對Java函數式編程的理解

常見的面試問題

總結一下,在Java程序員的面試中,經常會被問到類似這樣的問題:

  1. Java中的函數式接口是什麼意思?
  2. 註解 @FunctionalInterface 的作用是什麼?
  3. 實現一個函數式接口有哪幾種方式?
  4. lambda表達式和匿名內部類有什麼區別?
  5. Java中的方法引用有哪幾種形式?
  6. 能說說你對 Stream 接口中的 map 和 reduce 方法的理解嗎?
  7. Stream並行編程的底層實現用了什麼多線程框架?
  8. 能說說 Stream 並行編程的適用場景以及注意事項嗎?
  9. ConcurrentHashMap中,有哪些方法具備原子性?
  10. 為什麼在lambda表達式中引用外部變量時,要求外部變量是final的?怎麼繞開這個限制?

問題答案

  1. Java中的函數式接口是什麼意思?

只有一個抽象方法的接口都屬於函數式接口。英文術語為 Functional Interface 。

  1. 註解 @FunctionalInterface 的作用是什麼?

@FunctionalInterface 主要是告訴編譯器它修飾的接口是一個函數式接口,如果接口的定義不符合函數式接口的規範,那麼在編譯階段就會報錯。當然,我們也可以不加這個註解,對代碼的使用沒有任何影響。

  1. 實現一個函數式接口有哪幾種方式?

有3種方式:

  • 通過一個類來實現,包括常規的類和匿名內部類
  • 通過lambda表達式來實現
  • 通過方法引用來實現
  1. lambda表達式和匿名內部類有什麼區別?

匿名內部類本質是一個類,只是不需要程序員顯示指定類名,編譯器會自動為該類取名。而 lambda 表達式本質是一個函數,當然,編譯器也會為它取名。在JVM層面,匿名內部類對應的是一個 class 文件,而 lambda 表達式對應的是它所在主類的一個私有方法。

  1. Java中的方法引用有哪幾種形式?

有4種形式:

  • object::instanceMethod 對象 + 實例方法
  • ClassName::staticMethod 類名 + 靜態方法
  • ClassName::new 類名 + new關鍵字,構造方法引用
  • ClassName::instanceMethod 類名 + 實例方法
  1. 能說說你對 Stream 接口中的 map 和 reduce 方法的理解嗎?

map方法的作用是遍歷Stream中的每個元素,將每個元素映射為另一個元素。map,來源於數學中的概念函數映射。

reduce方法的作用是使用指定的計算邏輯,將多個元素逐個計算處理,最終得到一個結果。簡單來說,reduce的過程就是將多個元素轉換為一個最終結果。典型的包括將多個數字累加得到一個和,或者累乘得到一個積,或者將多個元素匯總起來得到一個ArrayList。

  1. Stream並行編程的底層實現用了什麼多線程技術?

使用了 ForkJoinPool 技術。ForkJoinPool是Java 7引入的用於並行執行的任務框架,核心思想是將一個大任務拆分成多個小任務(即fork),然後再將多個小任務的處理結果匯總到一個結果上(即join)。此外,它也提供基本的線程池功能,譬如設置最大並發線程數,關閉線程池等。

  1. 能說說 Stream 並行編程的優點和局限性嗎?

一般來說,在web應用中不推薦使用 Stream 的並行編程接口,因為 Stream 並行編程的底層是基於 ForkJoinPool ,而 ForkJoinPool 的工作線程數是在虛擬機啟動時指定的,如果 Stream 並行執行的任務數量過多或耗時過多,甚至會影響應用程序中其它使用 ForkJoinPool 的功能。

但如果是在某些任務單一、能確保不影響其它任務的場景中,使用 Stream 並行編程能帶來編碼上的便利性:即使數據源不是線程安全的,通過 Stream 並行編程,也能輕鬆寫出多線程並行處理任務的代碼,不需要考慮加鎖。

  1. ConcurrentHashMap中,有哪些方法具備原子性?

有4個方法具備原子性:

  • compute
  • computeIfAbsent
  • computeIfPresent
  • putIfAbsent

原子性的含義是說:從「判斷 ConcurrentHashMap 是否存在指定的key」開始,然後「計算對應的value」,最後「向 ConcurrentHashMap 寫入value」,這一系列的操作是一個原子性的過程 —— 就像加了鎖一樣,整個過程不會被別的線程打斷。

  1. 為什麼在lambda表達式中引用外部變量時,要求外部變量是final的?怎麼繞開這個限制?

在lambda表達式中引用外部變量,會形成一個閉包,在多線程環境下,容易導致線程安全問題,防不勝防。因此,Java規定了,在lambda表達式內部引用外部變量的話,必須是final的,即不可變對象,只能賦值一次,不可修改。

但是,我們可以通過將該外部變量聲明為一個數組或一個類(包括容器類)就可以修改其中的值。