国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

您的位置:首頁技術(shù)文章
文章詳情頁

詳解Java函數(shù)式編程和lambda表達(dá)式

瀏覽:7日期:2022-08-13 17:28:20
為什么要使用函數(shù)式編程

函數(shù)式編程更多時(shí)候是一種編程的思維方式,是種方法論。函數(shù)式與命令式編程的區(qū)別主要在于:函數(shù)式編程是告訴代碼你要做什么,而命令式編程則是告訴代碼要怎么做。說白了,函數(shù)式編程是基于某種語法或調(diào)用API去進(jìn)行編程。例如,我們現(xiàn)在需要從一組數(shù)字中,找出最小的那個(gè)數(shù)字,若使用用命令式編程實(shí)現(xiàn)這個(gè)需求的話,那么所編寫的代碼如下:

public static void main(String[] args) { int[] nums = new int[]{1, 2, 3, 4, 5, 6, 7, 8}; int min = Integer.MAX_VALUE; for (int num : nums) {if (num < min) { min = num;} } System.out.println(min);}

而使用函數(shù)式編程進(jìn)行實(shí)現(xiàn)的話,所編寫的代碼如下:

public static void main(String[] args) { int[] nums = new int[]{1, 2, 3, 4, 5, 6, 7, 8}; int min = IntStream.of(nums).min().getAsInt(); System.out.println(min);}

從以上的兩個(gè)例子中,可以看出,命令式編程需要自己去實(shí)現(xiàn)具體的邏輯細(xì)節(jié)。而函數(shù)式編程則是調(diào)用API完成需求的實(shí)現(xiàn),將原本命令式的代碼寫成一系列嵌套的函數(shù)調(diào)用,在函數(shù)式編程下顯得代碼更簡潔、易懂,這就是為什么要使用函數(shù)式編程的原因之一。所以才說函數(shù)式編程是告訴代碼你要做什么,而命令式編程則是告訴代碼要怎么做,是一種思維的轉(zhuǎn)變。

說到函數(shù)式編程就不得不提一下lambda表達(dá)式,它是函數(shù)式編程的基礎(chǔ)。在Java還不支持lambda表達(dá)式時(shí),我們需要創(chuàng)建一個(gè)線程的話,需要編寫如下代碼:

public static void main(String[] args) { new Thread(new Runnable() {@Overridepublic void run() { System.out.println('running');} }).start();}

而使用lambda表達(dá)式一句代碼就能完成線程的創(chuàng)建,lambda強(qiáng)調(diào)了函數(shù)的輸入輸出,隱藏了過程的細(xì)節(jié),并且可以接受函數(shù)當(dāng)作輸入(參數(shù))和輸出(返回值):

public static void main(String[] args) { new Thread(() -> System.out.println('running')).start();}

注:箭頭的左邊是輸入,右邊則是輸出。

該lambda表達(dá)式的作用其實(shí)就是返回了Runnable接口的實(shí)現(xiàn)對象,這與我們調(diào)用某個(gè)方法獲取實(shí)例對象類似,只不過是將實(shí)現(xiàn)代碼直接寫在了lambda表達(dá)式里。我們可以做個(gè)簡單的對比:

public static void main(String[] args) { Runnable runnable1 = () -> System.out.println('running'); Runnable runnable2 = RunnableFactory.getInstance();}JDK8接口新特性

1.函數(shù)接口,接口只能有一個(gè)需要實(shí)現(xiàn)的方法,可以使用@FunctionalInterface 注解進(jìn)行聲明。如下:

@FunctionalInterfaceinterface Interface1 { int doubleNum(int i);}

使用lambda表達(dá)式獲取該接口的實(shí)現(xiàn)實(shí)例的幾種寫法:

public static void main(String[] args) { // 最常見的寫法 Interface1 i1 = (i) -> i * 2; Interface1 i2 = i -> i * 2; // 可以指定參數(shù)類型 Interface1 i3 = (int i) -> i * 2; // 若有多行代碼可以這么寫 Interface1 i4 = (int i) -> {System.out.println(i);return i * 2; };}

2.比較重要的一個(gè)接口特性是接口的默認(rèn)方法,用于提供默認(rèn)實(shí)現(xiàn)。默認(rèn)方法和普通實(shí)現(xiàn)類的方法一樣,可以使用this等關(guān)鍵字:

@FunctionalInterfaceinterface Interface1 { int doubleNum(int i); default int add(int x, int y) {return x + y; }}

之所以說默認(rèn)方法這個(gè)特性比較重要,是因?yàn)槲覀兘柚@個(gè)特性可以在以前所編寫的一些接口上提供默認(rèn)實(shí)現(xiàn),并且不會影響任何的實(shí)現(xiàn)類以及既有的代碼。例如我們最熟悉的List接口,在JDK1.2以來List接口就沒有改動過任何代碼,到了1.8之后才使用這個(gè)新特性增加了一些默認(rèn)實(shí)現(xiàn)。這是因?yàn)槿绻麤]有默認(rèn)方法的特性的話,修改接口代碼帶來的影響是巨大的,而有了默認(rèn)方法后,增加默認(rèn)實(shí)現(xiàn)可以不影響任何的代碼。

3.當(dāng)接口多重繼承時(shí),可能會發(fā)生默認(rèn)方法覆蓋的問題,這時(shí)可以去指定使用哪一個(gè)接口的默認(rèn)方法實(shí)現(xiàn),如下示例:

@FunctionalInterfaceinterface Interface1 { int doubleNum(int i); default int add(int x, int y) {return x + y; }}@FunctionalInterfaceinterface Interface2 { int doubleNum(int i); default int add(int x, int y) {return x + y; }}@FunctionalInterfaceinterface Interface3 extends Interface1, Interface2 { @Override default int add(int x, int y) {// 指定使用哪一個(gè)接口的默認(rèn)方法實(shí)現(xiàn)return Interface1.super.add(x, y); }}函數(shù)接口

我們本小節(jié)來看看JDK8里自帶了哪些重要的函數(shù)接口:

詳解Java函數(shù)式編程和lambda表達(dá)式

可以看到上表中有好幾個(gè)接口,而其中最常用的是Function接口,它能為我們省去定義一些不必要的函數(shù)接口,減少接口的數(shù)量。我們使用一個(gè)簡單的例子演示一下 Function 接口的使用:

import java.text.DecimalFormat;import java.util.function.Function;class MyMoney { private final int money; public MyMoney(int money) {this.money = money; } public void printMoney(Function<Integer, String> moneyFormat) {System.out.println('我的存款: ' + moneyFormat.apply(this.money)); }}public class MoneyDemo { public static void main(String[] args) {MyMoney me = new MyMoney(99999999);Function<Integer, String> moneyFormat = i -> new DecimalFormat('#,###').format(i);// 函數(shù)接口支持鏈?zhǔn)讲僮鳎缭黾右粋€(gè)字符串me.printMoney(moneyFormat.andThen(s -> '人民幣 ' + s)); }}

運(yùn)行以上例子,控制臺輸出如下:

我的存款: 人民幣 99,999,999

若在這個(gè)例子中不使用Function接口的話,則需要自行定義一個(gè)函數(shù)接口,并且不支持鏈?zhǔn)讲僮鳎缦率纠?/p>

import java.text.DecimalFormat;// 自定義一個(gè)函數(shù)接口@FunctionalInterfaceinterface IMoneyFormat { String format(int i);}class MyMoney { private final int money; public MyMoney(int money) {this.money = money; } public void printMoney(IMoneyFormat moneyFormat) {System.out.println('我的存款: ' + moneyFormat.format(this.money)); }}public class MoneyDemo { public static void main(String[] args) {MyMoney me = new MyMoney(99999999);IMoneyFormat moneyFormat = i -> new DecimalFormat('#,###').format(i);me.printMoney(moneyFormat); }}

然后我們再來看看Predicate接口和Consumer接口的使用,如下示例:

public static void main(String[] args) { // 斷言函數(shù)接口 Predicate<Integer> predicate = i -> i > 0; System.out.println(predicate.test(-9)); // 消費(fèi)函數(shù)接口 Consumer<String> consumer = System.out::println; consumer.accept('這是輸入的數(shù)據(jù)');}

運(yùn)行以上例子,控制臺輸出如下:

false

這是輸入的數(shù)據(jù)

這些接口一般有對基本類型的封裝,使用特定類型的接口就不需要去指定泛型了,如下示例:

public static void main(String[] args) { // 斷言函數(shù)接口 IntPredicate intPredicate = i -> i > 0; System.out.println(intPredicate.test(-9)); // 消費(fèi)函數(shù)接口 IntConsumer intConsumer = (value) -> System.out.println('輸入的數(shù)據(jù)是:' + value); intConsumer.accept(123);}

運(yùn)行以上代碼,控制臺輸出如下:

false

輸入的數(shù)據(jù)是:123

有了以上接口示例的鋪墊,我們應(yīng)該對函數(shù)接口的使用有了一個(gè)初步的了解,接下來我們演示剩下的函數(shù)接口使用方式:

public static void main(String[] args) { // 提供數(shù)據(jù)接口 Supplier<Integer> supplier = () -> 10 + 1; System.out.println('提供的數(shù)據(jù)是:' + supplier.get()); // 一元函數(shù)接口 UnaryOperator<Integer> unaryOperator = i -> i * 2; System.out.println('計(jì)算結(jié)果為:' + unaryOperator.apply(10)); // 二元函數(shù)接口 BinaryOperator<Integer> binaryOperator = (a, b) -> a * b; System.out.println('計(jì)算結(jié)果為:' + binaryOperator.apply(10, 10));}

運(yùn)行以上代碼,控制臺輸出如下:

提供的數(shù)據(jù)是:11

計(jì)算結(jié)果為:20

計(jì)算結(jié)果為:100

而BiFunction接口就是比Function接口多了一個(gè)輸入而已,如下示例:

class MyMoney { private final int money; private final String name; public MyMoney(int money, String name) {this.money = money;this.name = name; } public void printMoney(BiFunction<Integer, String, String> moneyFormat) {System.out.println(moneyFormat.apply(this.money, this.name)); }}public class MoneyDemo { public static void main(String[] args) {MyMoney me = new MyMoney(99999999, '小明');BiFunction<Integer, String, String> moneyFormat = (i, name) -> name + '的存款: ' + new DecimalFormat('#,###').format(i);me.printMoney(moneyFormat); }}

運(yùn)行以上代碼,控制臺輸出如下:

小明的存款: 99,999,999

方法引用

在學(xué)習(xí)了lambda表達(dá)式之后,我們通常會使用lambda表達(dá)式來創(chuàng)建匿名方法。但有的時(shí)候我們僅僅是需要調(diào)用一個(gè)已存在的方法。如下示例:

Arrays.sort(stringsArray, (s1, s2) -> s1.compareToIgnoreCase(s2));

在jdk8中,我們可以通過一個(gè)新特性來簡寫這段lambda表達(dá)式。如下示例:

Arrays.sort(stringsArray, String::compareToIgnoreCase);

這種特性就叫做方法引用(Method Reference)。方法引用的標(biāo)準(zhǔn)形式是:類名::方法名。(注意:只需要寫方法名,不需要寫括號)。

目前方法引用共有以下四種形式:

類型 示例 代碼示例 對應(yīng)的Lambda表達(dá)式 引用靜態(tài)方法 ContainingClass::staticMethodName String::valueOf (s) -> String.valueOf(s) 引用某個(gè)對象的實(shí)例方法 containingObject::instanceMethodName x::toString() () -> this.toString() 引用某個(gè)類型的任意對象的實(shí)例方法 ContainingType::methodName String::toString (s) -> s.toString 引用構(gòu)造方法 ClassName::new String::new () -> new String()

下面我們用一個(gè)簡單的例子來演示一下方法引用的幾種寫法。首先定義一個(gè)實(shí)體類:

public class Dog { private String name = '二哈'; private int food = 10; public Dog() { } public Dog(String name) {this.name = name; } public static void bark(Dog dog) {System.out.println(dog + '叫了'); } public int eat(int num) {System.out.println('吃了' + num + '斤');this.food -= num;return this.food; } @Override public String toString() {return this.name; }}

通過方法引用來調(diào)用該實(shí)體類中的方法,代碼如下:

package org.zero01.example.demo;import java.util.function.*;/** * @ProjectName demo * @Author: zeroJun * @Date: 2018/9/21 13:09 * @Description: 方法引用demo */public class MethodRefrenceDemo { public static void main(String[] args) {// 方法引用,調(diào)用打印方法Consumer<String> consumer = System.out::println;consumer.accept('接收的數(shù)據(jù)');// 靜態(tài)方法引用,通過類名即可調(diào)用Consumer<Dog> consumer2 = Dog::bark;consumer2.accept(new Dog());// 實(shí)例方法引用,通過對象實(shí)例進(jìn)行引用Dog dog = new Dog();IntUnaryOperator function = dog::eat;System.out.println('還剩下' + function.applyAsInt(2) + '斤');// 另一種通過實(shí)例方法引用的方式,之所以可以這么干是因?yàn)镴DK默認(rèn)會把當(dāng)前實(shí)例傳入到非靜態(tài)方法,參數(shù)名為this,參數(shù)位置為第一個(gè),所以我們在非靜態(tài)方法中才能訪問this,那么就可以通過BiFunction傳入實(shí)例對象進(jìn)行實(shí)例方法的引用Dog dog2 = new Dog();BiFunction<Dog, Integer, Integer> biFunction = Dog::eat;System.out.println('還剩下' + biFunction.apply(dog2, 2) + '斤');// 無參構(gòu)造函數(shù)的方法引用,類似于靜態(tài)方法引用,只需要分析好輸入輸出即可Supplier<Dog> supplier = Dog::new;System.out.println('創(chuàng)建了新對象:' + supplier.get());// 有參構(gòu)造函數(shù)的方法引用Function<String, Dog> function2 = Dog::new;System.out.println('創(chuàng)建了新對象:' + function2.apply('旺財(cái)')); }}

最后需要說一句的就是能夠使用方法引用的地方就盡量不要使用lambda表達(dá)式,這樣就不會多生成一個(gè)類似 lambda$0這樣的函數(shù),能夠減少一些資源的開銷。

類型推斷

通過以上的例子,我們知道之所以能夠使用Lambda表達(dá)式的依據(jù)是必須有相應(yīng)的函數(shù)接口。這一點(diǎn)跟Java是強(qiáng)類型語言吻合,也就是說你并不能在代碼的任何地方任性的寫Lambda表達(dá)式。實(shí)際上Lambda的類型就是對應(yīng)函數(shù)接口的類型。Lambda表達(dá)式另一個(gè)依據(jù)是類型推斷機(jī)制,在上下文信息足夠的情況下,編譯器可以推斷出參數(shù)表的類型,而不需要顯式指名。所以說 Lambda 表達(dá)式的類型是從 Lambda 的上下文推斷出來的,上下文中 Lambda 表達(dá)式需要的類型稱為目標(biāo)類型,如下圖所示:

詳解Java函數(shù)式編程和lambda表達(dá)式

接下來我們使用一個(gè)簡單的例子,演示一下 Lambda 表達(dá)式的幾種類型推斷,首先定義一個(gè)簡單的函數(shù)接口:

@FunctionalInterfaceinterface IMath { int add(int x, int y);}

示例代碼如下:

public class TypeDemo { public static void main(String[] args) {// 1.通過變量類型定義IMath iMath = (x, y) -> x + y;// 2.數(shù)組構(gòu)建的方式IMath[] iMaths = {(x, y) -> x + y};// 3.強(qiáng)轉(zhuǎn)類型的方式Object object = (IMath) (x, y) -> x + y;// 4.通過方法返回值確定類型IMath result = createIMathObj();// 5.通過方法參數(shù)確定類型test((x, y) -> x + y); } public static IMath createIMathObj() {return (x, y) -> x + y; } public static void test(IMath iMath){return; }}變量引用

Lambda表達(dá)式類似于實(shí)現(xiàn)了指定接口的內(nèi)部類或者說匿名類,所以在Lambda表達(dá)式中引用變量和我們在匿名類中引用變量的規(guī)則是一樣的。如下示例:

public static void main(String[] args) { String str = '當(dāng)前的系統(tǒng)時(shí)間戳是: '; Consumer<Long> consumer = s -> System.out.println(str + s); consumer.accept(System.currentTimeMillis());}

值得一提的是,在JDK1.8之前我們一般會將匿名類里訪問的外部變量設(shè)置為final,而在JDK1.8里默認(rèn)會將這個(gè)匿名類里訪問的外部變量給設(shè)置為final。例如我現(xiàn)在改變str變量的值,ide就會提示錯(cuò)誤:

詳解Java函數(shù)式編程和lambda表達(dá)式

至于為什么要將變量設(shè)置final,這是因?yàn)樵贘ava里沒有引用傳遞,變量都是值傳遞的。不將變量設(shè)置為final的話,如果外部變量的引用被改變了,那么最終得出來的結(jié)果就會是錯(cuò)誤的。

下面用一組圖片簡單演示一下值傳遞與引用傳遞的區(qū)別。以列表為例,當(dāng)只是值傳遞時(shí),匿名類里對外部變量的引用是一個(gè)值對象:

詳解Java函數(shù)式編程和lambda表達(dá)式

若此時(shí)list變量指向了另一個(gè)對象,那么匿名類里引用的還是之前那個(gè)值對象,所以我們才需要將其設(shè)置為final防止外部變量引用改變:

詳解Java函數(shù)式編程和lambda表達(dá)式

而如果是引用傳遞的話,匿名類里對外部變量的引用就不是值對象了,而是指針指向這個(gè)外部變量:

詳解Java函數(shù)式編程和lambda表達(dá)式

所以就算list變量指向了另一個(gè)對象,匿名類里的引用也會隨著外部變量的引用改變而改變:

詳解Java函數(shù)式編程和lambda表達(dá)式

級聯(lián)表達(dá)式和柯里化

在函數(shù)式編程中,函數(shù)既可以接收也可以返回其他函數(shù)。函數(shù)不再像傳統(tǒng)的面向?qū)ο缶幊讨幸粯樱皇且粋€(gè)對象的工廠或生成器,它也能夠創(chuàng)建和返回另一個(gè)函數(shù)。返回函數(shù)的函數(shù)可以變成級聯(lián) lambda 表達(dá)式,特別值得注意的是代碼非常簡短。盡管此語法初看起來可能非常陌生,但它有自己的用途。

級聯(lián)表達(dá)式就是多個(gè)lambda表達(dá)式的組合,這里涉及到一個(gè)高階函數(shù)的概念,所謂高階函數(shù)就是一個(gè)可以返回函數(shù)的函數(shù),如下示例:

// 實(shí)現(xiàn)了 x + y 的級聯(lián)表達(dá)式Function<Integer, Function<Integer, Integer>> function1 = x -> y -> x + y;System.out.println('計(jì)算結(jié)果為: ' + function1.apply(2).apply(3)); // 計(jì)算結(jié)果為: 5

這里的 y -> x + y 是作為一個(gè)函數(shù)返回給上一級表達(dá)式,所以第一級表達(dá)式的輸出是 y -> x + y這個(gè)函數(shù),如果使用括號括起來可能會好理解一些:

x -> (y -> x + y)

級聯(lián)表達(dá)式可以實(shí)現(xiàn)函數(shù)柯里化,簡單來說柯里化就是把本來多個(gè)參數(shù)的函數(shù)轉(zhuǎn)換為只有一個(gè)參數(shù)的函數(shù),如下示例:

Function<Integer, Function<Integer, Function<Integer, Integer>>> function2 = x -> y -> z -> x + y + z;System.out.println('計(jì)算結(jié)果為: ' + function2.apply(1).apply(2).apply(3)); // 計(jì)算結(jié)果為: 6

如果大家想學(xué)習(xí)以上路線內(nèi)容,在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流群。交流學(xué)習(xí)群號874811168 里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備的知識體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源,目前受益良多

函數(shù)柯里化的目的是將函數(shù)標(biāo)準(zhǔn)化,函數(shù)可靈活組合,方便統(tǒng)一處理等,例如我可以在循環(huán)里只需要調(diào)用同一個(gè)方法,而不需要調(diào)用另外的方法就能實(shí)現(xiàn)一個(gè)數(shù)組內(nèi)元素的求和計(jì)算,代碼如下:

public static void main(String[] args) { Function<Integer, Function<Integer, Function<Integer, Integer>>> f3 = x -> y -> z -> x + y + z; int[] nums = {1, 2, 3}; for (int num : nums) {if (f3 instanceof Function) { Object obj = f3.apply(num); if (obj instanceof Function) {f3 = (Function) obj; } else {System.out.println('調(diào)用結(jié)束, 結(jié)果為: ' + obj); // 調(diào)用結(jié)束, 結(jié)果為: 6 }} }}

級聯(lián)表達(dá)式和柯里化一般在實(shí)際開發(fā)中并不是很常見,所以對其概念稍有理解即可,這里只是簡單帶過。

以上就是詳解Java函數(shù)式編程和lambda表達(dá)式的詳細(xì)內(nèi)容,更多關(guān)于Java函數(shù)式編程和lambda表達(dá)式的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 男女精品视频 | 中文毛片| 久青草免费视频 | 成人黄色在线网站 | 久久五月女厕所一区二区 | 久草在线观看视频 | 高清成人爽a毛片免费网站 高清大学生毛片一级 | 亚洲图片国产日韩欧美 | 免费人成在线观看视频不卡 | 免费观看视频成人国产 | 朝鲜美女免费一级毛片 | 天空在线观看免费完整 | 福利片免费一区二区三区 | 午夜免费理论片a级 | 免费一级a毛片在线播放视 免费一级α片在线观看 | 日本wwwwwwwww| 国产成人精品福利网站在线观看 | 黑人巨大交牲老太 | 波多野结衣在线不卡 | 日韩色综合 | 久久福利青草免费精品 | 97精品国产91久久久久久 | 手机看片高清国产日韩片 | 国产一区欧美二区 | 国产午夜毛片一区二区三区 | 99视频在线免费看 | 91精品国产手机 | 久久 精品 一区二区 | 美女张开腿 | 完全免费在线视频 | 亚洲性网站 | 黄色影院在线观看视频 | 日本一级在线播放线观看视频 | 国产91专区| 欧美一级第一免费高清 | 久久a热6| 在线观看国产精品日本不卡网 | 国产精品自在欧美一区 | 国产成人精品一区二三区 | 午夜在线影院 | 男人桶女人暴爽的视频 |