前言
何謂函數(shù)式編程
編程語言主要有三種類型:
命令式編程:專注于“如何去做”呜象,所有的事情都按照你的命令去做捅暴;
函數(shù)式編程:把運算的過程盡量表現(xiàn)成一系列的嵌套函數(shù)調(diào)用吉捶,相比命令式編程關(guān)心解決問題的步驟,它更關(guān)心數(shù)據(jù)之間的映射關(guān)系角钩,是面向數(shù)學的抽象赚抡;
邏輯式編程:它通過設定答案必須符合的規(guī)則來解決問題爬坑,過程是事實加上規(guī)則就等于最后的結(jié)果。
函數(shù)式編程并不是java提出的新概念涂臣,在java8之前盾计,我們關(guān)注的都是某一類對象有什么樣的特性(抽象數(shù)據(jù)),當然這也是面向?qū)ο缶幊痰幕A(chǔ)赁遗,在java8出現(xiàn)之后署辉,我們更加關(guān)注行為(抽象行為),這也是java8提出函數(shù)式編程的目的岩四。至于lambda表達式哭尝,其實也就是為了支持函數(shù)式編程的。
函數(shù)式接口
為了支持lambda表達式剖煌,函數(shù)式接口應運而生材鹦。所謂的函數(shù)式接口指的是有且只有一個未實現(xiàn)的方法的接口逝淹,一般可以通過FunctionalInterface這個注解來表明接口是一個函數(shù)式接口。函數(shù)式接口是整個函數(shù)式編程的基礎(chǔ)桶唐。java8之前其實就已經(jīng)有一些函數(shù)式接口了栅葡,比如java.lang.Runnable
,java.util.concurrent.Callable
尤泽,java.util.Comparator
等欣簇,java8更是提出了一些新的函數(shù)式接口:Supplier
,Consumer
坯约,Function
醉蚁,Predicate
。接下來本文將依次介紹java8提出的常用函數(shù)式接口鬼店,看看它們是怎么使我們的代碼更加簡潔更加優(yōu)雅的网棍。
Supplier
Supplier<T>代表了不接受任何參數(shù)就可以獲取一種類型返回結(jié)果的操作,類似于工廠方法妇智。它的方法定義如下:
T get();
我們來看個簡單的使用示例:
@org.junit.Test
public void testSupplier() {
Supplier<User> supplier = () -> new User("miaomiao", 24);
System.out.println(supplier.get());
}
class User {
/**
* 用戶名稱
*/
private String userName;
/**
* 用戶年齡
*/
private int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
Consumer
Consumer<T>代表了接受一個輸入?yún)?shù)并且無返回的操作滥玷,如果某一類操作不需要返回接口,可以對該類操作抽取邏輯巍棱。它常用的方法定義如下:
void accept(T t);
除了該方法外惑畴,Consumer還提供andThen
方法:
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
從方法實現(xiàn)可以看出,在當前Consumer可以指定后續(xù)調(diào)用的其他Consumer航徙。我們來通過一個簡單的例子來看下它是如何使用的:
@org.junit.Test
public void testConsumer() {
Consumer<String> before = System.out::println;
Consumer<String> after = n -> System.out.println(n + "_test");
//執(zhí)行before
before.accept("consumer");
//先執(zhí)行before再執(zhí)行after
before.andThen(after).accept("consumer");
}
運行結(jié)果:
consumer
consumer
consumer_test
Function
Function<T, R>代表了一個函數(shù)如贷,它接受一個輸入?yún)?shù)并且返回相應的一個結(jié)果。其中T代表函數(shù)輸入到踏,R代表函數(shù)輸出杠袱。它主要有以下四個方法:apply
,compose
窝稿,andThen
楣富,identity
:
//將Function對象應用到輸入的參數(shù)上,然后返回計算結(jié)果
R apply(T t);
//返回一個先執(zhí)行before函數(shù)對象的apply方法再執(zhí)行當前函數(shù)對象的apply方法的函數(shù)對象
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
//返回一個先執(zhí)行當前函數(shù)對象的apply方法再執(zhí)行after函數(shù)對象的apply方法的函數(shù)對象
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
//返回一個不經(jīng)過任何處理的函數(shù)對象伴榔,即輸入與輸出相等
static <T> Function<T, T> identity() {
return t -> t;
}
其中需要注意的是纹蝴,compose
和andThen
的不同之處就在于它們的函數(shù)調(diào)用順序是不一樣的。compose
先執(zhí)行參數(shù)函數(shù)再執(zhí)行調(diào)用者踪少,而andThen
則先執(zhí)行調(diào)用者再執(zhí)行參數(shù)塘安,這樣看起來可能比較繞,我們來個簡單的例子看一波:
@org.junit.Test
public void testFunction() {
Function<Integer, Integer> add = n -> (n + 10);
Function<Integer, Integer> square = n -> (n * n);
//計算n + 10并返回結(jié)果
System.out.println("add value = " + add.apply(10));
//計算n * n并返回結(jié)果
System.out.println("square value = " + square.apply(10));
//先計算n * n援奢,以其結(jié)果為參數(shù)計算n + 10
System.out.println("compose value = " + add.compose(square).apply(10));
//先計算n + 10兼犯,以其結(jié)果為參數(shù)計算n * n
System.out.println("andThen value = " + add.andThen(square).apply(10));
Object identity = Function.identity().apply("miaomiaoLoveCode");
System.out.println(identity);
}
我們來看下輸出結(jié)果:
add value = 20
square value = 100
compose value = 110
andThen value = 400
miaomiaoLoveCode
Predicate
Predicate<T>代表了接受一個輸入?yún)?shù),返回一個布爾值結(jié)果,主要用于判斷輸入?yún)?shù)是否滿足某種條件免都,滿足條件返回true锉罐,否則返回false。它有以下五個方法:test
绕娘,and
脓规,negate
,or
险领,isEqual
:
//判斷輸入?yún)?shù)是否滿足某種條件
boolean test(T t);
//與
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
//非
default Predicate<T> negate() {
return (t) -> !test(t);
}
//或
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
//判斷兩個值是否相等
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
還是來個例子看一波:
@org.junit.Test
public void testPredicate() {
Predicate<String> predicate1 = str -> str.equals("miao");
Predicate<String> predicate2 = str -> str.startsWith("m");
System.out.println(predicate1.test("miao"));
System.out.println(predicate2.test("miaomiao"));
System.out.println(predicate1.and(predicate2).test("miao"));
System.out.println(predicate1.negate().test("miaomiaoLoveCode"));
System.out.println(predicate1.or(predicate2).test("miaomiao"));
}
運行結(jié)果:
true
true
true
true
true
到這里為止侨舆,java8的函數(shù)式編程就掃盲完畢,看绢陌,使用函數(shù)式編程后我們的代碼是不是更簡短了挨下,其實代碼還可以更精簡些。ps:后文將會繼續(xù)介紹另外兩個精簡代碼神器:Stream和Optional脐湾,歡迎大家持續(xù)關(guān)注呀~