新聞中心
函數(shù)編程在C#、Python、JavaScript中都得到充分體現(xiàn)。而Java直到最新的Java 8才開(kāi)始正式支持函數(shù)編程,最明顯的改進(jìn)就是對(duì)Lamba表達(dá)式的支持。正如C#之父Anders Hejlsberg在那篇文章 編程語(yǔ)言大趨勢(shì) 中所講,未來(lái)的編程語(yǔ)言將逐漸融合各自的特性,而不存在單純的聲明式語(yǔ)言(如之前的Java)或者單純的函數(shù)編程語(yǔ)言。將來(lái)聲明式編程語(yǔ)言借鑒函數(shù)編程思想,函數(shù)編程語(yǔ)言融合聲明式編程特性...這幾乎是一種必然趨勢(shì)。如下圖所示:

公司主營(yíng)業(yè)務(wù):成都做網(wǎng)站、成都網(wǎng)站建設(shè)、移動(dòng)網(wǎng)站開(kāi)發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。成都創(chuàng)新互聯(lián)公司是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開(kāi)放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來(lái)驚喜。成都創(chuàng)新互聯(lián)公司推出和順免費(fèi)做網(wǎng)站回饋大家。
影響力較大的三個(gè)趨勢(shì)
那具體而言我們?yōu)槭裁葱枰狶ambda表達(dá)式呢?難道Java的OO和命令式編程(imperative programming)特性不夠強(qiáng)大嗎?下面讓我們來(lái)分析下其原因。
1、內(nèi)部循環(huán)和外部循環(huán)
先看一個(gè)大家耳熟能詳?shù)睦樱?/p>
- List
numbers = Arrays.asList(1, 2, 3, 4, 5, 6); - for (int number : numbers) {
- System.out.println(number);
- }
是不是很常見(jiàn)呢?這個(gè)叫外部循環(huán)(External Iteration)。但是外部循環(huán)有什么問(wèn)題呢?簡(jiǎn)單來(lái)說(shuō)存在下面三個(gè)缺點(diǎn):
1.只能順序處理List中的元素(process one by one)
2.不能充分利用多核CPU
3.不利于編譯器優(yōu)化
而如果利用內(nèi)部循環(huán),代碼寫(xiě)成下面這樣:
- List
numbers = Arrays.asList(1, 2, 3, 4, 5, 6); - numbers.forEach((Integer value) -> System.out.println(value));
這樣就能規(guī)避上面的三個(gè)問(wèn)題:
1.不一定需要順序處理List中的元素,順序可以不確定
2.可以并行處理,充分利用多核CPU的優(yōu)勢(shì)
3.有利于JIT編譯器對(duì)代碼進(jìn)行優(yōu)化
類似的C#從4.0版本開(kāi)始也支持集合元素并行處理,代碼如下:
- List
nums = new List { 1, 2, 3, 4, 5, 6 }; - Parallel.ForEach(nums, (value) =>
- {
- Console.WriteLine(value);
- });
#p#
2、傳遞行為,而不僅僅是傳值
如果你使用C#有一段時(shí)間的話,那么你很可能已經(jīng)明白這個(gè)標(biāo)題的意思了。在C#中,經(jīng)常看到一些函數(shù)的參數(shù)是Action或者Func類型,比如下面這個(gè):
- public class ArticleDac {
- ...
- public Article GetArticles(Func
, Article> func) // 這里傳遞的就是行為 - {
- using(var db = xx) {
- return func(db.Articles);
- }
- }
- ...
- }
- // 下面是調(diào)用
- int articleId = 119;
- var firstArticle = new ArticleDac().GetArticles(
- articleDbSet =>
- articleDbSet.AsQueryable().FirstOrDefault(x => x.id == articleId)
- );
看不懂?沒(méi)關(guān)系。我們先來(lái)看一個(gè)體現(xiàn)傳值局限性的場(chǎng)景吧,上代碼:
- List
numbers = Arrays.asList(1, 2, 3, 4, 5, 6); - public int sumAll(List
numbers) { - int total = 0;
- for (int number : numbers) {
- total += number;
- }
- return total;
- }
sumAll算法很簡(jiǎn)單,完成的是將List中所有元素相加。某一天如果我們需要增加一個(gè)對(duì)List中所有偶數(shù)求和的方法sumAllEven,如下:
- public int sumAllEven(List
numbers) { - int total = 0;
- for (int number : numbers) {
- if (number % 2 == 0) {
- total += number;
- }
- }
- return total;
- }
又有一天,我們需要增加第三個(gè)方法:對(duì)List中所有大于3的元素求和,那是不是繼續(xù)加下面的方法呢?
- public int sumAllEven(List
numbers) { - int total = 0;
- for (int number : numbers) {
- if (number > 3) {
- total += number;
- }
- }
- return total;
- }
比較這三個(gè)方法,我們發(fā)現(xiàn)了一個(gè)很明顯的“代碼臭味”—— 代碼重復(fù)(詳情參考《重構(gòu)》),三個(gè)方法的唯一區(qū)別在于if判斷這一行代碼。如果脫離這里的上下文,我們會(huì)怎么做呢?我首先會(huì)先想到利用策略模式重構(gòu)代碼如下:
- public interface Strategy {
- public boolean test(int num);
- }
- public class SumAllStrategy implements Strategy {
- public boolean test(int num) {
- return true;
- }
- }
- public class SumAllEvenStrategy implements Strategy {
- public boolean test(int num) {
- return num % 2 == 0;
- }
- }
- public class ContextClass {
- private Strategy stragegy = null;
- private final static Strategy DEFAULT_STRATEGY = new SumAllStrategy();
- public ContextClass() {
- this(null);
- }
- public ContextClass(Stragegy stragegy) {
- if(strategy != null) {
- this.strategy = strategy;
- }
- else {
- this.strategy = DEFAULT_STRATEGY;
- }
- }
- public int sumAll(List
numbers) { - int total = 0;
- for (int number : numbers) {
- if (strategy.test(number)) {
- total += number;
- }
- }
- return total;
- }
- }
- // 調(diào)用
- ContextClass context = new ContextClass();
- context.sumAll(numbers);
設(shè)計(jì)模式在這里發(fā)揮了作用,OO特性還是蠻強(qiáng)大的!但這是唯一的解決方案嗎(當(dāng)然不考慮用其他設(shè)計(jì)模式來(lái)解決,因?yàn)槎际荗O范疇!)?當(dāng)然有,該輪到Java 8 Lambda表達(dá)式中的謂詞(Predicate)發(fā)揮作用了!
- public int sumAll(List
numbers, Predicate p) { - int total = 0;
- for (int number : numbers) {
- if (p.test(number)) {
- total += number;
- }
- }
- return total;
- }
- sumAll(numbers, n -> true);
- sumAll(numbers, n -> n % 2 == 0);
- sumAll(numbers, n -> n > 3);
代碼是不是比上面簡(jiǎn)潔很多了?語(yǔ)義應(yīng)該也很明確,就不多解釋了,如果實(shí)在看不懂,請(qǐng)參考我的另外一篇文章: http://www.cnblogs.com/feichexia/archive/2012/11/15/Java8_LambdaExpression.html 從這里也可以看出未引入Lambda表達(dá)式之前的Java代碼的冗長(zhǎng)(Java這點(diǎn)被很多人詬?。?/p>
當(dāng)然C#早已經(jīng)支持這種用法,用C#改寫(xiě)上面的代碼如下:
- public int SumAll(IEnumerable
numbers, Predicate predicate) { - return numbers.Where(i => predicate(i)).Sum();
- }
- SumAll(numbers, n => true);
- SumAll(numbers, n => n % 2 == 0);
- SumAll(numbers, n => n > 3);
#p#
3、Consumer與Loan Pattern
比如我們有一個(gè)資源類Resource:
- public class Resource {
- public Resource() {
- System.out.println("Opening resource");
- }
- public void operate() {
- System.out.println("Operating on resource");
- }
- public void dispose() {
- System.out.println("Disposing resource");
- }
- }
我們必須這樣調(diào)用:
- Resource resource = new Resource();
- try {
- resource.operate();
- } finally {
- resource.dispose();
- }
因?yàn)閷?duì)資源對(duì)象resource執(zhí)行operate方法時(shí)可能拋出RuntimeException,所以需要在finally語(yǔ)句塊中釋放資源,防止可能的內(nèi)存泄漏。
但是有一個(gè)問(wèn)題,如果很多地方都要用到這個(gè)資源,那么就存在很多段類似這樣的代碼,這很明顯違反了DRY(Don't Repeat It Yourself)原則。而且如果某位程序員由于某些原因忘了用try/finally處理資源,那么很可能導(dǎo)致內(nèi)存泄漏。那咋辦呢?Java 8提供了一個(gè)Consumer接口,代碼改寫(xiě)為如下:
- public class Resource {
- private Resource() {
- System.out.println("Opening resource");
- }
- public void operate() {
- System.out.println("Operating on resource");
- }
- public void dispose() {
- System.out.println("Disposing resource");
- }
- public static void withResource(Consumer
consumer) { - Resource resource = new Resource();
- try {
- consumer.accept(resource);
- } finally {
- resource.dispose();
- }
- }
- }
調(diào)用代碼如下:
- Resource.withResource(resource -> resource.operate());
外部要訪問(wèn)Resource不能通過(guò)它的構(gòu)造函數(shù)了(private),只能通過(guò)withResource方法了,這樣代碼清爽多了,而且也完全杜絕了因人為疏忽而導(dǎo)致的潛在內(nèi)存泄漏。
#p#
4、stream+laziness => efficiency
像之前一樣先來(lái)一段非常簡(jiǎn)單的代碼:
- List
numbers = Arrays.asList(1, 2, 3, 4, 5, 6); - for (int number : numbers) {
- if (number % 2 == 0) {
- int n2 = number * 2;
- if (n2 > 5) {
- System.out.println(n2);
- break;
- }
- }
- }
這段代碼有什么問(wèn)題? 沒(méi)錯(cuò),可讀性非常差。第一步,我們利用《重構(gòu)》一書(shū)中的最基礎(chǔ)的提取小函數(shù)重構(gòu)手法來(lái)重構(gòu)代碼如下:
- public boolean isEven(int number) {
- return number % 2 == 0;
- }
- public int doubleIt(int number) {
- return number * 2;
- }
- public boolean isGreaterThan5(int number) {
- return number > 5;
- }
- for (int number : numbers) {
- if (isEven(number)) {
- int n2 = doubleIt(number);
- if (isGreaterThan5(n2)) {
- System.out.println(n2);
- break;
- }
- }
- }
OK,代碼的意圖清晰多了,但是可讀性仍然欠佳,因?yàn)檠h(huán)內(nèi)嵌套一個(gè)if分支,if分支內(nèi)又嵌套另外一個(gè)分支,于是繼續(xù)重構(gòu)代碼如下:
- public boolean isEven(int number) {
- return number % 2 == 0;
- }
- public int doubleIt(int number) {
- return number * 2;
- }
- public boolean isGreaterThan5(int number) {
- return number > 5;
- }
- List
l1 = new ArrayList (); - for (int n : numbers) {
- if (isEven(n)) l1.add(n);
- }
- List
l2 = new ArrayList (); - for (int n : l1) {
- l2.add(doubleIt(n));
- }
- List
l3 = new ArrayList (); - for (int n : l2) {
- if (isGreaterThan5(n)) l3.add(n);
- }
- System.out.println(l3.get(0));
現(xiàn)在代碼夠清晰了,這是典型的“流水線”風(fēng)格代碼。但是等等,現(xiàn)在的代碼執(zhí)行會(huì)占用更多空間(三個(gè)List)和時(shí)間,我們來(lái)分析下。首先第二版代碼的執(zhí)行流程是這樣的:
- isEven: 1
- isEven: 2
- doubleIt: 2
- isGreaterThan5: 2
- isEven: 3
- isEven: 4
- doubleIt: 4
- isGreaterThan5: 4
- 8
而我們的第三版代碼的執(zhí)行流程是這樣的:
- isEven: 1
- isEven: 2
- isEven: 3
- isEven: 4
- isEven: 5
- isEven: 6
- doubleIt: 2
- doubleIt: 4
- doubleIt: 6
- isGreaterThan5: 2
- isGreaterThan5: 4
- isGreaterThan5: 6
- 8
步驟數(shù)是13:9,所以有時(shí)候重構(gòu)得到可讀性強(qiáng)的代碼可能會(huì)犧牲一些運(yùn)行效率(但是一切都得實(shí)際衡量之后才能確定)。那么有沒(méi)有“三全其美”的實(shí)現(xiàn)方法呢?即:
1.代碼可讀性強(qiáng)
2.代碼執(zhí)行效率不比第一版代碼差
3.空間消耗小
Streams come to rescue! Java 8提供了stream方法,我們可以通過(guò)對(duì)任何集合對(duì)象調(diào)用stream()方法獲得Stream對(duì)象,Stream對(duì)象有別于Collections的幾點(diǎn)如下:
1.不存儲(chǔ)值:Streams不會(huì)存儲(chǔ)值,它們從某個(gè)數(shù)據(jù)結(jié)構(gòu)的流水線型操作中獲取值(“酒肉穿腸過(guò)”)
2.天生的函數(shù)編程特性:對(duì)Stream對(duì)象操作能得到一個(gè)結(jié)果,但是不會(huì)修改原始數(shù)據(jù)結(jié)構(gòu)
3.Laziness-seeking(延遲搜索):Stream的很多操作如filter、map、sort和duplicate removal(去重)可以延遲實(shí)現(xiàn),意思是我們只要檢查到滿足要求的元素就可以返回
4.可選邊界:Streams允許Client取足夠多的元素直到滿足某個(gè)條件為止。而Collections不能這么做
上代碼:
- System.out.println(
- numbers.stream()
- .filter(Lazy::isEven)
- .map(Lazy::doubleIt)
- .filter(Lazy::isGreaterThan5)
- .findFirst()
- );
現(xiàn)在的執(zhí)行流程是:
- isEven: 1
- isEven: 2
- doubleIt: 2
- isGreaterThan5: 4
- isEven: 3
- isEven: 4
- doubleIt: 4
- isGreaterThan5: 8
- IntOptional[8]
流程基本和第二版代碼一致,這歸功于Laziness-seeking特性。怎么理解呢?讓我來(lái)構(gòu)造下面這個(gè)場(chǎng)景:
- Stream流對(duì)象要經(jīng)過(guò)下面這種流水線式處理:
- 過(guò)濾出偶數(shù) => 乘以2 => 過(guò)濾出大于5的數(shù) => 取出第一個(gè)數(shù)
- 注意:=> 左邊的輸出是右邊的輸入
而Laziness-seeking意味著 我們?cè)诿恳徊街灰徽业綕M足條件的數(shù)字,馬上傳遞給下一步去處理并且暫停當(dāng)前步驟。比如先判斷1是否偶數(shù),顯然不是;繼續(xù)判斷2是否偶數(shù),是偶數(shù);好,暫停過(guò)濾偶數(shù)操作,將2傳遞給下一步乘以2,得到4;4繼續(xù)傳遞給第三步,4不滿足大于5,所以折回第一步;判斷3是否偶數(shù),不是;判斷4是否偶數(shù),是偶數(shù);4傳遞給第二步,乘以2得到8;8傳遞給第三步,8大于5;所以傳遞給最后一步,直接取出得到 IntOptional[8]。
IntOptional[8]只是簡(jiǎn)單包裝了下返回的結(jié)果,這樣有什么好處呢?如果你接觸過(guò)Null Object Pattern的話就知道了,這樣可以避免無(wú)謂的null檢測(cè)。
參考自:
http://java.dzone.com/articles/why-we-need-lambda-expressions
http://java.dzone.com/articles/why-we-need-lambda-expressions-0
當(dāng)前題目:Java8為什么需要Lambda表達(dá)式
文章出自:http://fisionsoft.com.cn/article/cooeecs.html


咨詢
建站咨詢
