Java8新特性:函數式編程
首先需要清楚一個概念:函數式接口;它指的是有且隻有一個未實現的方法的接口,一般通過FunctionalInterface這個註解來表明某個接口是一個函數式接口。函數式接口是Java支持函數式編程的基礎。
1 Java8函數式編程語法入門
Java8中函數式編程語法能夠精簡代碼。
使用Consumer作為示例,它是一個函數式接口,包含一個抽象方法accept,這個方法隻有輸入而無輸出。
現在我們要定義一個Consumer對象,傳統的方式是這樣定義的:
Consumer c = new Consumer() { @Override public void accept(Object o) { System.out.println(o); } };
而在Java8中,針對函數式編程接口,可以這樣定義:
Consumer c = (o) -> { System.out.println(o); };
上面已說明,函數式編程接口都隻有一個抽象方法,因此在采用這種寫法時,編譯器會將這段函數編譯後當作該抽象方法的實現。
如果接口有多個抽象方法,編譯器就不知道這段函數應該是實現哪個方法的瞭。
因此,=後面的函數體我們就可以看成是accept函數的實現。
- 輸入:->前面的部分,即被()包圍的部分。此處隻有一個輸入參數,實際上輸入是可以有多個的,如兩個參數時寫法:(a, b);當然也可以沒有輸入,此時直接就可以是()。
- 函數體:->後面的部分,即被{}包圍的部分;可以是一段代碼。
- 輸出:函數式編程可以沒有返回值,也可以有返回值。如果有返回值時,需要代碼段的最後一句通過return的方式返回對應的值。
當函數體中隻有一個語句時,可以去掉{}進一步簡化:
Consumer c = (o) -> System.out.println(o);
然而這還不是最簡的,由於此處隻是進行打印,調用瞭System.out中的println靜態方法對輸入參數直接進行打印,因此可以簡化成以下寫法:
Consumer c = System.out::println;
它表示的意思就是針對輸入的參數將其調用System.out中的靜態方法println進行打印。
到這一步就可以感受到函數式編程的強大能力。
通過最後一段代碼,我們可以簡單的理解函數式編程,Consumer接口直接就可以當成一個函數瞭,這個函數接收一個輸入參數,然後針對這個輸入進行處理;當然其本質上仍舊是一個對象,但我們已經省去瞭諸如老方式中的對象定義過程,直接使用一段代碼來給函數式接口對象賦值。
而且最為關鍵的是,這個函數式對象因為本質上仍舊是一個對象,因此可以做為其它方法的參數或者返回值,可以與原有的代碼實現無縫集成!
下面對Java中的幾個預先定義的函數式接口及其經常使用的類進行分析學習。
2 Java函數式接口
2.1 Consumer
Consumer是一個函數式編程接口; 顧名思義,Consumer的意思就是消費,即針對某個東西我們來使用它,因此它包含有一個有輸入而無輸出的accept接口方法;
除accept方法,它還包含有andThen這個方法;
其定義如下:
default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; }
可見這個方法就是指定在調用當前Consumer後是否還要調用其它的Consumer;
使用示例:
public static void consumerTest() { Consumer f = System.out::println; Consumer f2 = n -> System.out.println(n + "-F2"); //執行完F後再執行F2的Accept方法 f.andThen(f2).accept("test"); //連續執行F的Accept方法 f.andThen(f).andThen(f).andThen(f).accept("test1"); }
2.2 Function
Function也是一個函數式編程接口;它代表的含義是“函數”,而函數經常是有輸入輸出的,因此它含有一個apply方法,包含一個輸入與一個輸出;
除apply方法外,它還有compose與andThen及indentity三個方法,其使用見下述示例;
/** * Function測試 */ public static void functionTest() { Function<Integer, Integer> f = s -> s++; Function<Integer, Integer> g = s -> s * 2; /** * 下面表示在執行F時,先執行G,並且執行F時使用G的輸出當作輸入。 * 相當於以下代碼: * Integer a = g.apply(1); * System.out.println(f.apply(a)); */ System.out.println(f.compose(g).apply(1)); /** * 表示執行F的Apply後使用其返回的值當作輸入再執行G的Apply; * 相當於以下代碼 * Integer a = f.apply(1); * System.out.println(g.apply(a)); */ System.out.println(f.andThen(g).apply(1)); /** * identity方法會返回一個不進行任何處理的Function,即輸出與輸入值相等; */ System.out.println(Function.identity().apply("a")); }
2.3 Predicate
Predicate為函數式接口,predicate的中文意思是“斷定”,即判斷的意思,判斷某個東西是否滿足某種條件; 因此它包含test方法,根據輸入值來做邏輯判斷,其結果為True或者False。
它的使用方法示例如下:
/** * Predicate測試 */ private static void predicateTest() { Predicate<String> p = o -> o.equals("test"); Predicate<String> g = o -> o.startsWith("t"); /** * negate: 用於對原來的Predicate做取反處理; * 如當調用p.test("test")為True時,調用p.negate().test("test")就會是False; */ Assert.assertFalse(p.negate().test("test")); /** * and: 針對同一輸入值,多個Predicate均返回True時返回True,否則返回False; */ Assert.assertTrue(p.and(g).test("test")); /** * or: 針對同一輸入值,多個Predicate隻要有一個返回True則返回True,否則返回False */ Assert.assertTrue(p.or(g).test("ta")); }
3 函數式編程接口的使用
通過Stream以及Optional兩個類,可以進一步利用函數式接口來簡化代碼。
3.1 Stream
Stream可以對多個元素進行一系列的操作,也可以支持對某些操作進行並發處理。
3.1.1 Stream對象的創建
Stream對象的創建途徑有以下幾種
a. 創建空的Stream對象
Stream stream = Stream.empty();
b. 通過集合類中的stream或者parallelStream方法創建;
List<String> list = Arrays.asList("a", "b", "c", "d"); Stream listStream = list.stream(); //獲取串行的Stream對象 Stream parallelListStream = list.parallelStream(); //獲取並行的Stream對象
c. 通過Stream中的of方法創建:
Stream s = Stream.of("test"); Stream s1 = Stream.of("a", "b", "c", "d");
d. 通過Stream中的iterate方法創建:
iterate方法有兩個不同參數的方法:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f); public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
其中第一個方法將會返回一個無限有序值的Stream對象:它的第一個元素是seed,第二個元素是f.apply(seed); 第N個元素是f.apply(n-1個元素的值);生成無限值的方法實際上與Stream的中間方法類似,在遇到中止方法前一般是不真正的執行的。因此無限值的這個方法一般與limit等方法一起使用,來獲取前多少個元素。
當然獲取前多少個元素也可以使用第二個方法。
第二個方法與第一個方法生成元素的方式類似,不同的是它返回的是一個有限值的Stream;中止條件是由hasNext來斷定的。
第二種方法的使用示例如下:
/** * 本示例表示從1開始組裝一個序列,第一個是1,第二個是1+1即2,第三個是2+1即3..,直接10時中止; * 也可簡化成以下形式: * Stream.iterate(1, * n -> n <= 10, * n -> n+1).forEach(System.out::println); * 寫成以下方式是為簡化理解 */ Stream.iterate(1, new Predicate<Integer>() { @Override public boolean test(Integer integer) { return integer <= 10; } }, new UnaryOperator<Integer>() { @Override public Integer apply(Integer integer) { return integer+1; } }).forEach(System.out::println);
e. 通過Stream中的generate方法創建
與iterate中創建無限元素的Stream類似,不過它的每個元素與前一元素無關,且生成的是一個無序的隊列。也就是說每一個元素都可以隨機生成。因此一般用來創建常量的Stream以及隨機的Stream等。
示例如下:
/** * 隨機生成10個Double元素的Stream並將其打印 */ Stream.generate(new Supplier<Double>() { @Override public Double get() { return Math.random(); } }).limit(10).forEach(System.out::println); //上述寫法可以簡化成以下寫法: Stream.generate(() -> Math.random()).limit(10).forEach(System.out::println);
f. 通過Stream中的concat方法連接兩個Stream對象生成新的Stream對象 這個比較好理解不再贅述。
3.1.2 Stream對象的使用
Stream對象提供多個非常有用的方法,這些方法可以分成兩類:
中間操作:將原始的Stream轉換成另外一個Stream;如filter返回的是過濾後的Stream。
終端操作:產生的是一個結果或者其它的復合操作;如count或者forEach操作。
其清單如下所示,方法的具體說明及使用示例見後文。
所有中間操作
方法 | 說明 |
---|---|
sequential | 返回一個相等的串行的Stream對象,如果原Stream對象已經是串行就可能會返回原對象 |
parallel | 返回一個相等的並行的Stream對象,如果原Stream對象已經是並行的就會返回原對象 |
unordered | 返回一個不關心順序的Stream對象,如果原對象已經是這類型的對象就會返回原對象 |
onClose | 返回一個相等的Steam對象,同時新的Stream對象在執行Close方法時會調用傳入的Runnable對象 |
close | 關閉Stream對象 |
filter | 元素過濾:對Stream對象按指定的Predicate進行過濾,返回的Stream對象中僅包含未被過濾的元素 |
map | 元素一對一轉換:使用傳入的Function對象對Stream中的所有元素進行處理,返回的Stream對象中的元素為原元素處理後的結果 |
mapToInt | 元素一對一轉換:將原Stream中的使用傳入的IntFunction加工後返回一個IntStream對象 |
flatMap | 元素一對多轉換:對原Stream中的所有元素進行操作,每個元素會有一個或者多個結果,然後將返回的所有元素組合成一個統一的Stream並返回; |
distinct | 去重:返回一個去重後的Stream對象 |
sorted | 排序:返回排序後的Stream對象 |
peek | 使用傳入的Consumer對象對所有元素進行消費後,返回一個新的包含所有原來元素的Stream對象 |
limit | 獲取有限個元素組成新的Stream對象返回 |
skip | 拋棄前指定個元素後使用剩下的元素組成新的Stream返回 |
takeWhile | 如果Stream是有序的(Ordered),那麼返回最長命中序列(符合傳入的Predicate的最長命中序列)組成的Stream;如果是無序的,那麼返回的是所有符合傳入的Predicate的元素序列組成的Stream。 |
dropWhile | 與takeWhile相反,如果是有序的,返回除最長命中序列外的所有元素組成的Stream;如果是無序的,返回所有未命中的元素組成的Stream。 |
所有終端操作
方法 | 說明 |
---|---|
iterator | 返回Stream中所有對象的迭代器; |
spliterator | 返回對所有對象進行的spliterator對象 |
forEach | 對所有元素進行迭代處理,無返回值 |
forEachOrdered | 按Stream的Encounter所決定的序列進行迭代處理,無返回值 |
toArray | 返回所有元素的數組 |
reduce | 使用一個初始化的值,與Stream中的元素一一做傳入的二合運算後返回最終的值。每與一個元素做運算後的結果,再與下一個元素做運算。它不保證會按序列執行整個過程。 |
collect | 根據傳入參數做相關匯聚計算 |
min | 返回所有元素中最小值的Optional對象;如果Stream中無任何元素,那麼返回的Optional對象為Empty |
max | 與Min相反 |
count | 所有元素個數 |
anyMatch | 隻要其中有一個元素滿足傳入的Predicate時返回True,否則返回False |
allMatch | 所有元素均滿足傳入的Predicate時返回True,否則False |
noneMatch | 所有元素均不滿足傳入的Predicate時返回True,否則False |
findFirst | 返回第一個元素的Optioanl對象;如果無元素返回的是空的Optional; 如果Stream是無序的,那麼任何元素都可能被返回。 |
findAny | 返回任意一個元素的Optional對象,如果無元素返回的是空的Optioanl。 |
isParallel | 判斷是否當前Stream對象是並行的 |
下面就幾個比較常用的方法舉例說明其用法:
3.1.2.1 filter
用於對Stream中的元素進行過濾,返回一個過濾後的Stream
其方法定義如下:
Stream<T> filter(Predicate<? super T> predicate);
使用示例:
Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa"); //查找所有包含t的元素並進行打印 s.filter(n -> n.contains("t")).forEach(System.out::println);
3.1.2.2 map
元素一對一轉換。
它接收一個Funcation參數,用其對Stream中的所有元素進行處理,返回的Stream對象中的元素為Function對原元素處理後的結果
其方法定義如下:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
示例,假設我們要將一個String類型的Stream對象中的每個元素添加相同的後綴.txt,如a變成a.txt,其寫法如下:
Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa"); s.map(n -> n.concat(".txt")).forEach(System.out::println);
3.1.2.3 flatMap
元素一對多轉換:對原Stream中的所有元素使用傳入的Function進行處理,每個元素經過處理後生成一個多個元素的Stream對象,然後將返回的所有Stream對象中的所有元素組合成一個統一的Stream並返回;
方法定義如下:
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
示例,假設要對一個String類型的Stream進行處理,將每一個元素的拆分成單個字母,並打印:
Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa"); s.flatMap(n -> Stream.of(n.split(""))).forEach(System.out::println);
3.1.2.4 takeWhile
方法定義如下:
default Stream<T> takeWhile(Predicate<? super T> predicate)
如果Stream是有序的(Ordered),那麼返回最長命中序列(符合傳入的Predicate的最長命中序列)組成的Stream;如果是無序的,那麼返回的是所有符合傳入的Predicate的元素序列組成的Stream。
與Filter有點類似,不同的地方就在當Stream是有序時,返回的隻是最長命中序列。
如以下示例,通過takeWhile查找”test”, “t1”, “t2”, “teeeee”, “aaaa”, “taaa”這幾個元素中包含t的最長命中序列:
Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa"); //以下結果將打印: "test", "t1", "t2", "teeeee",最後的那個taaa不會進行打印 s.takeWhile(n -> n.contains("t")).forEach(System.out::println);
3.1.2.5 dropWhile
與takeWhile相反,如果是有序的,返回除最長命中序列外的所有元素組成的Stream;如果是無序的,返回所有未命中的元素組成的Stream;其定義如下:
default Stream<T> dropWhile(Predicate<? super T> predicate)
如以下示例,通過dropWhile刪除”test”, “t1”, “t2”, “teeeee”, “aaaa”, “taaa”這幾個元素中包含t的最長命中序列:
Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa"); //以下結果將打印:"aaaa", "taaa" s.dropWhile(n -> n.contains("t")).forEach(System.out::println);
3.1.2.6 reduce與collect
關於reduce與collect由於功能較為復雜,在後續將進行單獨分析與學習,此處暫不涉及。
3.2 Optional
用於簡化Java中對空值的判斷處理,以防止出現各種空指針異常。
Optional實際上是對一個變量進行封裝,它包含有一個屬性value,實際上就是這個變量的值。
3.2.1 Optional對象創建
它的構造函數都是private類型的,因此要初始化一個Optional的對象無法通過其構造函數進行創建。它提供瞭一系列的靜態方法用於構建Optional對象:
3.2.1.1 empty
用於創建一個空的Optional對象;其value屬性為Null。
如:
Optional o = Optional.empty();
3.2.1.2 of
根據傳入的值構建一個Optional對象;
傳入的值必須是非空值,否則如果傳入的值為空值,則會拋出空指針異常。
使用:
o = Optional.of("test");
3.2.1.3 ofNullable
根據傳入值構建一個Optional對象
傳入的值可以是空值,如果傳入的值是空值,則與empty返回的結果是一樣的。
3.2.2 方法
Optional包含以下方法:
方法名 | 說明 |
---|---|
get | 獲取Value的值,如果Value值是空值,則會拋出NoSuchElementException異常;因此返回的Value值無需再做空值判斷,隻要沒有拋出異常,都會是非空值。 |
isPresent | Value是否為空值的判斷; |
ifPresent | 當Value不為空時,執行傳入的Consumer; |
ifPresentOrElse | Value不為空時,執行傳入的Consumer;否則執行傳入的Runnable對象; |
filter | 當Value為空或者傳入的Predicate對象調用test(value)返回False時,返回Empty對象;否則返回當前的Optional對象 |
map | 一對一轉換:當Value為空時返回Empty對象,否則返回傳入的Function執行apply(value)後的結果組裝的Optional對象; |
flatMap | 一對多轉換:當Value為空時返回Empty對象,否則傳入的Function執行apply(value)後返回的結果(其返回結果直接是Optional對象) |
or | 如果Value不為空,則返回當前的Optional對象;否則,返回傳入的Supplier生成的Optional對象; |
stream | 如果Value為空,返回Stream對象的Empty值;否則返回Stream.of(value)的Stream對象; |
orElse | Value不為空則返回Value,否則返回傳入的值; |
orElseGet | Value不為空則返回Value,否則返回傳入的Supplier生成的值; |
orElseThrow | Value不為空則返回Value,否則拋出Supplier中生成的異常對象; |
3.2.3 使用場景
常用的使用場景如下:
3.2.3.1 判斷結果不為空後使用
如某個函數可能會返回空值,以往的做法:
String s = test(); if (null != s) { System.out.println(s); }
現在的寫法就可以是:
Optional<String> s = Optional.ofNullable(test()); s.ifPresent(System.out::println);
乍一看代碼復雜度上差不多甚至是略有提升;那為什麼要這麼做呢?
一般情況下,我們在使用某一個函數返回值時,要做的第一步就是去分析這個函數是否會返回空值;如果沒有進行分析或者分析的結果出現偏差,導致函數會拋出空值而沒有做檢測,那麼就會相應的拋出空指針異常!
而有瞭Optional後,在我們不確定時就可以不用去做這個檢測瞭,所有的檢測Optional對象都幫忙我們完成,我們要做的就是按上述方式去處理。
3.2.3.2 變量為空時提供默認值
如要判斷某個變量為空時使用提供的值,然後再針對這個變量做某種運算;
以往做法:
if (null == s) { s = "test"; } System.out.println(s);
現在的做法:
Optional<String> o = Optional.ofNullable(s); System.out.println(o.orElse("test"));
3.2.3.3 變量為空時拋出異常,否則使用
以往寫法:
if (null == s) { throw new Exception("test"); } System.out.println(s);
現在寫法:
Optional<String> o = Optional.ofNullable(s); System.out.println(o.orElseThrow(()->new Exception("test")));
其它場景待補充。
總結
這邊文章是寫關於Java8新出的特性:函數式編程的內容,包含Stream,Function,Optional,Consumer等內容,更多相關Java8新特性的內容請搜索WalkonNet其他文章