Java8-函數(shù)式編程
為什么java8 的Lambda 這么受關(guān)注制妄?
Java8可以用Lambda表達(dá)式很簡便的寫出復(fù)雜的處理集合的邏輯缨恒。Lambda 可以理解為一種匿名函數(shù)的代替蚕钦。使用它可以簡化代碼,提高開發(fā)效率。
函數(shù)編程演化歷程
函數(shù)編程演化歷程
- 1.將業(yè)務(wù)邏輯直接寫死在代碼里。
- 2.將單一維度的條件做為參數(shù)傳入方法中灵嫌。方法內(nèi)根據(jù)參數(shù)進(jìn)行業(yè)務(wù)邏輯實(shí)現(xiàn)。
- 3.將多個維度的條件做為參數(shù)傳入方法中冀偶。業(yè)務(wù)實(shí)現(xiàn)需要根據(jù)不同的參數(shù)處理不同邏輯醒第。
- 4.將業(yè)務(wù)邏輯封裝為一個實(shí)體類,方法接受實(shí)體類為參數(shù)进鸠,方法內(nèi)部調(diào)用實(shí)體類的處理邏輯稠曼。
- 5.調(diào)用方法時不在創(chuàng)建實(shí)體類,而是使用匿名函數(shù)的形式替代客年。
- 6.使用Lambda表達(dá)式替代匿名函數(shù)的形式霞幅,做為方法的參數(shù)。真正實(shí)現(xiàn)判斷邏輯參數(shù)化傳遞量瓜。
函數(shù)編程
函數(shù)式編程是一種編程范式司恳,我們常見的編程范式有命令式編程(Imperative programming),函數(shù)式編程绍傲,邏輯式編程扔傅,常見的面向?qū)ο缶幊淌且彩且环N命令式編程。
命令式編程是面向計算機(jī)硬件的抽象烫饼,有變量(對應(yīng)著存儲單元)猎塞,賦值語句(獲取,存儲指令)杠纵,表達(dá)式(內(nèi)存引用和算術(shù)運(yùn)算)和控制語句(跳轉(zhuǎn)指令)荠耽,一句話,命令式程序就是一個馮諾依曼機(jī)的指令序列比藻。而函數(shù)式編程是面向數(shù)學(xué)的抽象铝量,將計算描述為一種表達(dá)式求值倘屹,一句話,函數(shù)式程序就是一個表達(dá)式慢叨。
函數(shù)式編程中的函數(shù)這個術(shù)語不是指計算機(jī)中的函數(shù)(實(shí)際上是Subroutine)纽匙,而是指數(shù)學(xué)中的函數(shù),即自變量的映射插爹。也就是說一個函數(shù)的值僅決定于函數(shù)參數(shù)的值哄辣,不依賴其他狀態(tài)。比如sqrt(x)函數(shù)計算x的平方根赠尾,只要x不變力穗,不論什么時候調(diào)用,調(diào)用幾次气嫁,值都是不變的当窗。在函數(shù)式語言中,函數(shù)作為一等公民寸宵,可以在任何地方定義崖面,在函數(shù)內(nèi)或函數(shù)外,可以作為函數(shù)的參數(shù)和返回值梯影,可以對函數(shù)進(jìn)行組合巫员。
Lambda表達(dá)式
Lambda表達(dá)式的兩種形式
(parameters) -> expression
(parameters) ->{ statements; }
以下是lambda表達(dá)式的重要特征:可選類型聲明:不需要聲明參數(shù)類型月匣,編譯器可以統(tǒng)一識別參數(shù)值啥纸。
可選的參數(shù)圓括號:一個參數(shù)無需定義圓括號,但多個參數(shù)需要定義圓括號忿危。
可選的大括號:如果主體包含了一個語句感猛,就不需要使用大括號七扰。
可選的返回關(guān)鍵字:如果主體只有一個表達(dá)式返回值則編譯器會自動返回值,大括號需要指定明表達(dá)式返回了一個數(shù)值陪白。
表達(dá)式實(shí)例
// 1. 不需要參數(shù)
() -> 5 ;
() -> System.out.println("hello world");
// 2. 接收一個參數(shù)
x -> 2 * x ;
() -> System.out.println("hello world");
// 3. 沒有參數(shù)邏輯復(fù)雜
() -> {
System.out.println("hello world1");
System.out.println("hello world2");
}
// 3. 接受2個參數(shù)(數(shù)字)
BinaryOperator<Long> functionAdd = (x,y)-> x + y ;
Long result = functionAdd(1L,2L);
// 4. 接收2個int型整數(shù),顯示聲明參數(shù)類型
(int x, int y) -> x + y
// 5. 接受一個 string 對象,并在控制臺打印,不返回任何值(看起來像是返回void)
(String s) -> System.out.print(s)
以上就是lambda的介紹颈走。
函數(shù)式接口介紹
通常Lambda表達(dá)式是用在函數(shù)式接口上使用的。從Java8開始引入了函數(shù)式接口咱士,其說明比較簡單:函數(shù)式接口(Functional Interface)就是一個有且僅有一個抽象方法立由,但是可以有多個非抽象方法的接口。 java8引入@FunctionalInterface 注解聲明該接口是一個函數(shù)式接口序厉。
語法定義
@FunctionalInterface
public interface ICollectionService {
void test();
}
常用的函數(shù)式接口
接口 | 參數(shù) | 返回類型 | 描述 |
---|---|---|---|
Predicate<T> | T | boolean | 用于判別一個對象 |
Consumer<T> | T | void | 用于接收一個對象進(jìn)行處理但沒有返回 |
Function<T,R> | T | R | 轉(zhuǎn)換一個對象為不同類型的對象 |
Supplier<T> | None | T | 提供一個對象 |
UnaryOperator<T> | T | T | 接收對象并返回同類型的對象 |
BinaryOperator<T> | (T,T) | T | 接收兩個同類型的對象锐膜,并返回一個原類型的對象 |
Predicate
java.util.function.Predicate<T> 接口定義了一個名叫 test 的抽象方法,它接受泛型 T 對象脂矫,并返回一個boolean值。在對類型 T進(jìn)行斷言判斷時霉晕,可以使用這個接口庭再。通常稱為斷言性接口捞奕。
例如
@FunctionalInterface
public interface PredicateTest<T> {
boolean test(T t);
}
@SpringBootTest
class LambdaTestApplicationTests {
//借助Lambda 表達(dá)式實(shí)現(xiàn)Predicate test方法
@Test
public void test3(){
PredicateTest<String> predicateTest = (str)->str.isEmpty()||str.trim().isEmpty();
System.out.println(predicateTest.test(""));
System.out.println(predicateTest.test(" "));
System.out.println(predicateTest.test(" as"));
}
}
Consumer
java.util.function.Consumer<T>接口定義了一個名叫 accept 的抽象方法,它接受泛型T拄轻,沒有返回值(void)颅围。如果需要訪問類型 T 的對象,并對其執(zhí)行某些操作恨搓,可以使用這個接口院促,通常稱為消費(fèi)性接口。
@FunctionalInterface
public interface ConsumerTest<T> {
void accept(T t);
}
/**
* 借助Lambda表達(dá)式實(shí)現(xiàn)Consumer accept方法
*/
@Test
public void test4(){
ConsumerTest<Collection> consumerTest = collection ->{
if (!CollectionUtils.isEmpty(collection)){
for (Object o : collection) {
System.out.println(o);
}
}
};
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
consumerTest.accept(list);
}
Function
java.util.function.Function<T, R>接口定義了一個叫作apply的方法斧抱,它接受一個泛型T的對象常拓,并返回一個泛型R的對象。如果需要定義一個Lambda辉浦,將輸入的信息映射到輸出弄抬,可以使用這個接口(比如提取蘋果的重量,或把字符串映射為它的長度),通常稱為功能性接口宪郊。
@FunctionalInterface
public interface FunctionTest<T,R> {
R apply(T t);
}
@Test
public void test5() {
FunctionTest<String, String> functionTest = password -> Base64.getEncoder().encodeToString(password.getBytes());
System.out.println(functionTest.apply("123456"));
}
Supplier
java.util.function.Supplier<T>接口定義了一個get的抽象方法掂恕,它沒有參數(shù),返回一個泛型T的對象弛槐,這類似于一個工廠方法,通常稱為功能性接口懊亡。
@FunctionalInterface
public interface SupplierTest<T> {
T get();
}
@Test
public void test6(){
int[] arr = {100,0,-50,880,99,33,-30};
SupplierTest<Integer> s = ()->{
int max = arr[0];
//遍歷數(shù)組,獲取數(shù)組中的其他元素
for (int i : arr) {
//使用其他的元素和最大值比較
if(i>max){
//如果i大于max,則替換max作為最大值
max = i;
}
}
//返回最大值
return max;
};
System.out.println(s.get().intValue());
}
UnaryOperator
java.util.function.UnaryOperator<T> 定義了一個identity方法.
這個接口,只接收一個泛型參數(shù)T乎串,集成Function接口店枣,也就是說,傳入泛型T類型的參數(shù)灌闺,調(diào)用apply后艰争,返回也T類型的參數(shù);這個接口定義了一個靜態(tài)方法桂对,返回泛型對象的本身甩卓;
@Test
public void test7() {
UnaryOperator<Integer> test = x -> x + 100;
System.out.println(test.apply(100));
}
BinaryOperator
java.util.function.BinaryOperator<T>接口用于執(zhí)行l(wèi)ambda表達(dá)式并返回一個T類型的返回值。
/**
* 接收兩個同類型的對象蕉斜,并返回一個原類型的對象
*/
@Test
public void test8(){
BinaryOperator<Integer> add = (n1, n2) -> n1 + n2;
//apply方法用于接收參數(shù)逾柿,并返回BinaryOperator中的Integer類型
System.out.println(add.apply(3, 4));
}
BiConsumer
這個接口接收兩個泛型參數(shù),跟Consumer一樣宅此,都有一個 accept方法机错,只不過,這里的父腕,接收兩個泛型參數(shù)弱匪,對這兩個參數(shù)做下消費(fèi)處理;使用這個函數(shù)式接口的終端操作有map的遍歷璧亮;下面看下面的例子萧诫,兩個參數(shù)消費(fèi)數(shù)據(jù)的.可以看到斥难,Map接口的終端操作,forEach的參數(shù)就是BiConsumer函數(shù)接口帘饶,對HashMap 的數(shù)據(jù)進(jìn)行消費(fèi)哑诊;BiConsumer函數(shù)接口還有一個默認(rèn)函數(shù),andThen及刻,接收一個BiConsumer接口镀裤,先執(zhí)行本接口的,再執(zhí)行傳入的參數(shù)缴饭。
Map<String, String> map = new HashMap<>();
map.put("a", "a");
map.put("b", "b");
map.put("c", "c");
map.put("d", "d");
map.forEach((k, v) -> {
System.out.println(k);
System.out.println(v);
});
方法引用
有時候我們不是必須要自己重寫某個匿名內(nèi)部類的方法暑劝,我們可以可以利用 lambda表達(dá)式的接口快速指向一個已經(jīng)被實(shí)現(xiàn)的方法。 調(diào)用特定方法的lambda表達(dá)式的一種快捷寫法茴扁,可以讓你重復(fù)使用現(xiàn)有的方法定義铃岔,并像lambda表達(dá)式一樣傳遞他們。
語法:
方法歸屬者::方法名
靜態(tài)方法的歸屬者為類名峭火,普通方法歸屬者為對象
指向靜態(tài)方法的方法引用
public void test(){
Consumer<String> consumer = (String s) -> Integer.parseInt(s);
Consumer<String> consumer1 = Integer::parseInt;
}
指向?qū)ο蟮膶?shí)例方法引用
StringBuilder stringBuilder = new StringBuilder();
Consumer<String> consumer = (String s) -> stringBuilder.append(s);
Consumer<String> consumer1 = stringBuilder::append;