Java语法糖设计
語法糖
????Java語法糖系列,所以首先講講什么是語法糖。語法糖是一種幾乎每種語言或多或少都提供過的一些方便程序員開發(fā)代碼的語法,它只是編譯器實(shí)現(xiàn)的一些小把戲罷了,編譯期間以特定的字節(jié)碼或者特定的方式對(duì)這些語法做一些處理,開發(fā)者就可以直接方便地使用了。這些語法糖雖然不會(huì)提供實(shí)質(zhì)性的功能改進(jìn),但是它們或能提高性能、或能提升語法的嚴(yán)謹(jǐn)性、或能減少編碼出錯(cuò)的機(jī)會(huì)。Java提供給了用戶大量的語法糖,比如泛型、自動(dòng)裝箱、自動(dòng)拆箱、foreach循環(huán)、變長(zhǎng)參數(shù)、內(nèi)部類、枚舉類、斷言(assert)等
?
?
?
斷言(assert)
開啟斷言
-
用java?命令在console下直接運(yùn)行class文件,跟?-ea?啟動(dòng)參數(shù)即可
參考文章
-
單獨(dú)給某個(gè)程序制定運(yùn)行參數(shù)
-
給整個(gè)java運(yùn)行環(huán)境配置默認(rèn)參數(shù)
?
一、
?
語法形式
Java2在1.4中新增了一個(gè)關(guān)鍵字:assert。在程序開發(fā)過程中使用它創(chuàng)建一個(gè)斷言(assertion),它的
語法形式有如下所示的兩種形式:
1、assert condition;
這里condition是一個(gè)必須為真(true)的表達(dá)式。如果表達(dá)式的結(jié)果為true,那么斷言為真,并且無任何行動(dòng)
如果表達(dá)式為false,則斷言失敗,則會(huì)拋出一個(gè)AssertionError對(duì)象。這個(gè)AssertionError繼承于Error對(duì)象,
而Error繼承于Throwable,Error是和Exception并列的一個(gè)錯(cuò)誤對(duì)象,通常用于表達(dá)系統(tǒng)級(jí)運(yùn)行錯(cuò)誤。
2、asser condition:expr;
這里condition是和上面一樣的,這個(gè)冒號(hào)后跟的是一個(gè)表達(dá)式,通常用于斷言失敗后的提示信息,說白了,它是一個(gè)傳到AssertionError構(gòu)造函數(shù)的值,如果斷言失敗,該值被轉(zhuǎn)化為它對(duì)應(yīng)的字符串,并顯示出來。
使用示例:
?
?
?
語法改進(jìn)
Foreach與變長(zhǎng)參數(shù)
?
Foreach和邊長(zhǎng)參數(shù)都是語法糖。
For Array循環(huán)是標(biāo)準(zhǔn)的數(shù)組下標(biāo)循環(huán)的語法糖。
For Collection?是迭代器的語法糖。
?
?
可變長(zhǎng)參數(shù),每次都會(huì)初始化一個(gè)參數(shù)長(zhǎng)度數(shù)組,并申請(qǐng)內(nèi)存和賦值。在多次調(diào)用的情況這個(gè)消耗是沒有必要且明顯的。應(yīng)當(dāng)根據(jù)提供固定參數(shù)個(gè)數(shù)的方法。
關(guān)于返回值的問題。
基本類型拆箱裝箱
true | false的原因
Integer的享元設(shè)計(jì),上限取決于業(yè)務(wù)數(shù)字使用范圍。
空指針原因是integer.intValue();這是個(gè)對(duì)象方法
?
內(nèi)部類與資源自動(dòng)管理
字節(jié)碼文件中沒有其他類信息,只有本類方法和類描述,內(nèi)部類表
自動(dòng)關(guān)閉資源在字節(jié)碼文件中還是生成了close方法
?
泛型(Generic)
????支持創(chuàng)建可以按類型進(jìn)行參數(shù)化的類.可以把類型參數(shù)看作是使用參數(shù)類型時(shí)指定的類型占位符,泛型能保證大型應(yīng)用程序的類型安全和良好的維護(hù)性。Java泛型的實(shí)現(xiàn)是語法糖,在編譯完成后并沒有保留參數(shù)化類型的信息。因此你可以通過反射獲取后添加非泛型數(shù)據(jù),當(dāng)然一般不會(huì)這么做。泛型的好處在于:可以定義一類數(shù)據(jù)集合,進(jìn)行相同的操作,如果非泛型成員,在編譯時(shí)候就可以檢測(cè);可以在聲明時(shí)指定具體類型,這樣避免寫過多的子類。
泛型可以在方法和類(接口、抽象類)上聲明。語法為<T>?,T表示泛型,可以是任意字母,一般T便是type類型,E表示元素等。在運(yùn)行時(shí)T已經(jīng)確定。
????數(shù)組與泛型
數(shù)組是協(xié)變的,例如:參數(shù)類型為Object[],表示可以傳任意數(shù)組。如果Sub為Super的子類型,那么數(shù)組類型Sub[]就是Super[]的子類型。List<Sub>與List<Super>并沒有什么關(guān)系。
如果進(jìn)行類型轉(zhuǎn)換,數(shù)組在運(yùn)行時(shí)才知道具體的類型,這會(huì)導(dǎo)致ArrayStoreException異常,而泛型在編譯期間檢查錯(cuò)誤,確保為某一類型。
泛型的運(yùn)行時(shí)擦除,導(dǎo)致不能定義泛型數(shù)組。因?yàn)檫\(yùn)行時(shí)并不保存泛型類型信息。
?
泛型用法
List<E>、List<?>與原生List。
泛型的上限與下限
?
枚舉
創(chuàng)建枚舉類型要使用?enum?關(guān)鍵字,隱含了所創(chuàng)建的類型都是?java.lang.Enum?類的子類(java.lang.Enum?是一個(gè)抽象類)。枚舉類型符合通用模式Class Enum<E extends Enum<E>>,而?E?表示枚舉類型的名稱。枚舉類型的每一個(gè)值都將映射到?protected Enum(String name, int ordinal)?構(gòu)造函數(shù)中,在這里,每個(gè)值的名稱都被轉(zhuǎn)換成一個(gè)字符串,并且序數(shù)設(shè)置表示了此設(shè)置被創(chuàng)建的順序。但是并不推薦顯示調(diào)用序數(shù)作為枚舉映射列表。
簡(jiǎn)介
這個(gè)例子描述人類的性別,人類的性別記錄應(yīng)該為三種男、女、不確定。枚舉的元素是確定的而且在加載類的時(shí)候就會(huì)初始化為一個(gè)對(duì)象。枚舉對(duì)象跟普通的類對(duì)象,沒有任何區(qū)別。你可以增加一個(gè)方法,做一些操作。我們一般利用枚舉對(duì)象是特定做一些除了數(shù)據(jù)枚舉外的高級(jí)操作。
枚舉中可以只有枚舉屬性。
每個(gè)枚舉對(duì)象會(huì)走對(duì)應(yīng)的構(gòu)造方法。不能new?枚舉對(duì)象。通過枚舉的官方定義,枚舉是按照創(chuàng)建順序的。
?
?
應(yīng)用
在使用int常量作為枚舉是,我們通過大寫的名稱對(duì)應(yīng)相應(yīng)的int值,表述此類使用的多個(gè)常量。不同類型的int枚舉類型可以用命名前綴來區(qū)分。這種做法性能好,省力,可讀性也很好。但它作為人為的口頭約束。如果沒有了解約束,放任何int值是可能的,甚至用枚舉做運(yùn)算。外部調(diào)用時(shí)可能出錯(cuò)。再者,int枚舉類型并不能很好的表述含義,它至少一個(gè)0,1,2,4等等。你不知道它的名稱。
Java的枚舉類型,即天然是int值類型,又支持名稱的意義。而且枚舉作為類,你可以非常方便的拓展需要的成員或者方法。枚舉final并不提供實(shí)例化的構(gòu)造器,讓它完全避免了反射等可能造成的對(duì)象實(shí)例受限破壞。枚舉的值為順勢(shì)的transient,序列化不會(huì)造成枚舉常量實(shí)例多份的危險(xiǎn)。設(shè)計(jì)非常巧妙。
如果需要用枚舉做映射,實(shí)現(xiàn)高性能的類似位圖的枚舉映射數(shù)據(jù)結(jié)構(gòu)可以參考EnumSet<E extends Enum<E>>、EnumMap<K extends Enum<K>,V>。
?
語法糖的實(shí)現(xiàn)
初始化,枚舉三原色,成員變量name,index
按需要定義普通方法
枚舉中關(guān)鍵的屬性
輸出結(jié)果
利用枚舉對(duì)象實(shí)例的確定性質(zhì),枚舉元素只有一個(gè)就是一個(gè)規(guī)范的單例,你可以按需定義成員。此種方式可以保證該單例線程安全、防反射攻擊、防止序列化生成新的實(shí)例。
枚舉元素的初始化過程
枚舉類編譯后的類中沒有任何構(gòu)造方法。自然沒辦法進(jìn)行反射new對(duì)象。下面是普通對(duì)象的構(gòu)造方法。
枚舉禁用clone保證唯一性
關(guān)于序列化的問題
Java枚舉序列化不會(huì)把枚舉對(duì)象序列化,只會(huì)序列化枚舉的名字,反序列化會(huì)把常量作為參數(shù)。來保證只有枚舉對(duì)象的唯一性。1.12 Serialization of Enum Constants
?
?
枚舉的擴(kuò)展
?
為了方便使用,在接口中添加default方法,需要jdk1.8
這個(gè)寫法調(diào)用兩次PLUS,但在編譯期間可以檢測(cè)實(shí)例是否是枚舉和Operation接口類型。
Lambda
本文介紹了Java SE 8中新引入的lambda語言特性以及這些特性背后的設(shè)計(jì)思想。這些特性包括:
lambda表達(dá)式(又被成為"閉包"或"匿名方法")
方法引用和構(gòu)造方法引用
擴(kuò)展的目標(biāo)類型和類型推導(dǎo)
接口中的默認(rèn)方法和靜態(tài)方法
1.?背景
Java是一門面向?qū)ο缶幊陶Z言。面向?qū)ο缶幊陶Z言和函數(shù)式編程語言中的基本元素(Basic Values)都可以動(dòng)態(tài)封裝程序行為:面向?qū)ο缶幊陶Z言使用帶有方法的對(duì)象封裝行為,函數(shù)式編程語言使用函數(shù)封裝行為。但這個(gè)相同點(diǎn)并不明顯,因?yàn)镴ava的對(duì)象往往比較"重量級(jí)":實(shí)例化一個(gè)類型往往會(huì)涉及不同的類,并需要初始化類里的字段和方法。不過有些Java對(duì)象只是對(duì)單個(gè)函數(shù)的封裝。
?
Lambda表達(dá)式是一段可以傳遞的代碼
場(chǎng)景一:線程邏輯
場(chǎng)景二:自定義比較器
?
場(chǎng)景三:事件按鈕回調(diào)
幻
2. 函數(shù)式接口(Functional interfaces)
可以通過@FunctionalInterface注解來顯式指定一個(gè)接口是函數(shù)式接口。????我們選擇了"使用已知類型"這條路——因?yàn)楝F(xiàn)有的類庫大量使用了函數(shù)式接口,通過沿用這種模式,我們使得現(xiàn)有類庫能夠直接使用lambda表達(dá)式。例如下面是Java SE 7中已經(jīng)存在的函數(shù)式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.beans.PropertyChangeListener
除此之外,Java SE 8中增加了一個(gè)新的包:java.util.function,它里面包含了常用的函數(shù)式接口,例如:
- Predicate<T>——接收T對(duì)象并返回boolean
- Consumer<T>——接收T對(duì)象,不返回值
- Function<T, R>——接收T對(duì)象,返回R對(duì)象
- Supplier<T>——提供T對(duì)象(例如工廠),不接收值
- UnaryOperator<T>——接收T對(duì)象,返回T對(duì)象
- BinaryOperator<T>——接收兩個(gè)T對(duì)象,返回T對(duì)象
- IntSupplier,LongBinaryOperator——原始類型(Primitive type)的特化(Specialization)函數(shù)式接口
- BiFunction<T, U, R>——接收T對(duì)象和U對(duì)象,返回R對(duì)象
匿名類型最大的問題就在于其冗余的語法。有人戲稱匿名類型導(dǎo)致了"高度問題"(height problem),lambda表達(dá)式是匿名方法,它提供了輕量級(jí)的語法,從而解決了匿名內(nèi)部類帶來的"高度問題"。
下面是一些lambda表達(dá)式:
第一個(gè)lambda表達(dá)式接收x和y這兩個(gè)整形參數(shù)并返回它們的和;第二個(gè)lambda表達(dá)式不接收參數(shù),返回整數(shù)'42';第三個(gè)lambda表達(dá)式接收一個(gè)字符串并把它打印到控制臺(tái),不返回值。
lambda表達(dá)式的語法由參數(shù)列表、箭頭符號(hào)->和函數(shù)體組成。函數(shù)體既可以是一個(gè)表達(dá)式,也可以是一個(gè)語句塊:
- 表達(dá)式:表達(dá)式會(huì)被執(zhí)行然后返回執(zhí)行結(jié)果。
-
語句塊:語句塊中的語句會(huì)被依次執(zhí)行,就像方法中的語句一樣——
- return語句會(huì)把控制權(quán)交給匿名方法的調(diào)用者
- break和continue只能在循環(huán)中使用
- 如果函數(shù)體有返回值,那么函數(shù)體內(nèi)部的每一條路徑都必須返回值
表達(dá)式函數(shù)體適合小型lambda表達(dá)式,它消除了return關(guān)鍵字,使得語法更加簡(jiǎn)潔。
lambda表達(dá)式也會(huì)經(jīng)常出現(xiàn)在嵌套環(huán)境中,比如說作為方法的參數(shù)。為了使lambda表達(dá)式在這些場(chǎng)景下盡可能簡(jiǎn)潔,我們?nèi)コ瞬槐匾姆指舴2贿^在某些情況下我們也可以把它分為多行,然后用括號(hào)包起來,就像其它普通表達(dá)式一樣。
下面是一些出現(xiàn)在語句中的lambda表達(dá)式:
3.?目標(biāo)類型(Target typing)
對(duì)于給定的lambda表達(dá)式的類型是由其上下文推導(dǎo)而來,這符合
這就意味著同樣的lambda表達(dá)式在不同上下文里可以擁有不同的類型:
第一個(gè)lambda表達(dá)式() -> "done"是Callable的實(shí)例,而第二個(gè)lambda表達(dá)式則是PrivilegedAction的實(shí)例。
編譯器負(fù)責(zé)推導(dǎo)lambda表達(dá)式的類型。它利用lambda表達(dá)式所在上下文所期待的類型進(jìn)行推導(dǎo),這個(gè)被期待的類型被稱為目標(biāo)類型。lambda表達(dá)式只能出現(xiàn)在目標(biāo)類型為函數(shù)式接口的上下文中。
當(dāng)然,lambda表達(dá)式對(duì)目標(biāo)類型也是有要求的。編譯器會(huì)檢查lambda表達(dá)式的類型和目標(biāo)類型的方法簽名(method signature)是否一致。當(dāng)且僅當(dāng)下面所有條件均滿足時(shí),lambda表達(dá)式才可以被賦給目標(biāo)類型T:
lambda表達(dá)式內(nèi)所拋出的異常和T的方法throws類型相兼容
由于目標(biāo)類型(函數(shù)式接口)已經(jīng)"知道"lambda表達(dá)式的形式參數(shù)(Formal parameter)類型,所以我們沒有必要把已知類型再重復(fù)一遍。也就是說,lambda表達(dá)式的參數(shù)類型可以從目標(biāo)類型中得出:
在上面的例子里,編譯器可以推導(dǎo)出s1和s2的類型是String。此外,當(dāng)lambda的參數(shù)只有一個(gè)而且它的類型可以被推導(dǎo)得知時(shí),該參數(shù)列表外面的括號(hào)可以被省略:
在前三個(gè)上下文(變量聲明、賦值和返回語句)里,目標(biāo)類型即是被賦值或被返回的類型:
數(shù)組初始化器和賦值類似,只是這里的"變量"變成了數(shù)組元素,而類型是從數(shù)組類型中推導(dǎo)得知:
方法參數(shù)的類型推導(dǎo)要相對(duì)復(fù)雜些:目標(biāo)類型的確認(rèn)會(huì)涉及到其它兩個(gè)語言特性:重載解析(Overload resolution)和參數(shù)類型推導(dǎo)(Type argument inference)。
重載解析會(huì)為一個(gè)給定的方法調(diào)用(method invocation)尋找最合適的方法聲明(method declaration)。由于不同的聲明具有不同的簽名,當(dāng)lambda表達(dá)式作為方法參數(shù)時(shí),重載解析就會(huì)影響到lambda表達(dá)式的目標(biāo)類型。編譯器會(huì)通過它所得之的信息來做出決定。如果lambda表達(dá)式具有顯式類型(參數(shù)類型被顯式指定),編譯器就可以直接 使用lambda表達(dá)式的返回類型;如果lambda表達(dá)式具有隱式類型(參數(shù)類型被推導(dǎo)而知),重載解析則會(huì)忽略lambda表達(dá)式函數(shù)體而只依賴lambda表達(dá)式參數(shù)的數(shù)量。
如果在解析方法聲明時(shí)存在二義性(ambiguous),我們就需要利用轉(zhuǎn)型(cast)或顯式lambda表達(dá)式來提供更多的類型信息。如果lambda表達(dá)式的返回類型依賴于其參數(shù)的類型,那么lambda表達(dá)式函數(shù)體有可能可以給編譯器提供額外的信息,以便其推導(dǎo)參數(shù)類型。
在上面的代碼中,ps的類型是List<Person>,所以ps.stream()的返回類型是Stream<Person>。map()方法接收一個(gè)類型為Function<T, R>的函數(shù)式接口,這里T的類型即是Stream元素的類型,也就是Person,而R的類型未知。由于在重載解析之后lambda表達(dá)式的目標(biāo)類型仍然未知,我們就需要推導(dǎo)R的類型:通過對(duì)lambda表達(dá)式函數(shù)體進(jìn)行類型檢查,我們發(fā)現(xiàn)函數(shù)體返回String,因此R的類型是String,因而map()返回Stream<String>。絕大多數(shù)情況下編譯器都能解析出正確的類型,但如果碰到無法解析的情況,我們則需要:
- 使用顯式lambda表達(dá)式(為參數(shù)p提供顯式類型)以提供額外的類型信息
- 把lambda表達(dá)式轉(zhuǎn)型為Function<Person, String>
- 為泛型參數(shù)R提供一個(gè)實(shí)際類型。(.<String>map(p -> p.getName()))
lambda表達(dá)式本身也可以為它自己的函數(shù)體提供目標(biāo)類型,也就是說lambda表達(dá)式可以通過外部目標(biāo)類型推導(dǎo)出其內(nèi)部的返回類型,這意味著我們可以方便的編寫一個(gè)返回函數(shù)的函數(shù):
類似的,條件表達(dá)式可以把目標(biāo)類型"分發(fā)"給其子表達(dá)式:
在無法確認(rèn)目標(biāo)類型時(shí),轉(zhuǎn)型表達(dá)式(Cast expression)可以顯式提供lambda表達(dá)式的類型:
除此之外,當(dāng)重載的方法都擁有函數(shù)式接口時(shí),轉(zhuǎn)型可以幫助解決重載解析時(shí)出現(xiàn)的二義性。
目標(biāo)類型這個(gè)概念不僅僅適用于lambda表達(dá)式,泛型方法調(diào)用和"菱形"構(gòu)造方法調(diào)用也可以從目標(biāo)類型中受益,下面的代碼在Java SE 7是非法的,但在Java SE 8中是合法的:
局部變量
基于詞法作用域的理念,lambda表達(dá)式所在上下文中的局部變量不可以重復(fù)定義。
在Java SE 7中,編譯器對(duì)內(nèi)部類中引用的外部變量(即捕獲的變量)要求被聲明為final。對(duì)于lambda表達(dá)式和內(nèi)部類,我們?cè)试S在其中捕獲那些符合有效只讀(Effectively final)的局部變量。
如果一個(gè)局部變量在初始化后從未被修改過,那么它就符合有效只讀的要求,換句話說,加上final后也不會(huì)導(dǎo)致編譯錯(cuò)誤的局部變量就是有效只讀變量。
為什么要禁止這種行為呢?因?yàn)檫@樣的lambda表達(dá)式很容易引起race condition。lambda expressions close over values, not variables:
lambda表達(dá)式不支持修改捕獲變量的另一個(gè)原因是我們可以使用更好的方式來實(shí)現(xiàn)同樣的效果:使用規(guī)約(reduction)。java.util.stream包提供了各種通用的和專用的規(guī)約操作(例如sum、min和max),就上面的例子而言,我們可以使用規(guī)約操作(在串行和并行下都是安全的)來代替forEach:
sum()等價(jià)于下面的規(guī)約操作:
規(guī)約需要一個(gè)初始值(以防輸入為空)和一個(gè)操作符(在這里是加號(hào)),然后用下面的表達(dá)式計(jì)算結(jié)果:
規(guī)約也可以完成其它操作,比如求最小值、最大值和乘積等等。如果操作符具有可結(jié)合性(associative),那么規(guī)約操作就可以容易的被并行化。所以,與其支持一個(gè)本質(zhì)上是并行而且容易導(dǎo)致race condition的操作,我們選擇在庫中提供一個(gè)更加并行友好且不容易出錯(cuò)的方式來進(jìn)行累積(accumulation)。
4.?方法引用(Method references)
方法引用有很多種,它們的語法如下:
- 靜態(tài)方法引用:ClassName::methodName
- 實(shí)例上的實(shí)例方法引用:instanceReference::methodName
- 超類上的實(shí)例方法引用:super::methodName
- 類型上的實(shí)例方法引用:ClassName::methodName
- 構(gòu)造方法引用:Class::new
- 數(shù)組構(gòu)造方法引用:TypeName[]::new
構(gòu)造方法也可以通過new關(guān)鍵字被直接引用:
數(shù)組的構(gòu)造方法引用的語法則比較特殊,為了便于理解,你可以假想存在一個(gè)接收int參數(shù)的數(shù)組構(gòu)造方法。參考下面的代碼:
5.?默認(rèn)方法和靜態(tài)接口方法(Default and static interface methods)
向函數(shù)式接口里增加默認(rèn)方法,所有實(shí)現(xiàn)接口的類都自動(dòng)繼承這個(gè)默認(rèn)方法并調(diào)用。
下面的例子展示了如何向Iterator接口增加默認(rèn)方法skip:
除了默認(rèn)方法,Java SE 8還在允許在接口中定義靜態(tài)方法。這使得我們可以從接口直接調(diào)用和它相關(guān)的輔助方法(Helper method),而不是從其它的類中調(diào)用(之前這樣的類往往以對(duì)應(yīng)接口的復(fù)數(shù)命名,例如Collections)。比如,我們一般需要使用靜態(tài)輔助方法生成實(shí)現(xiàn)Comparator的比較器,在Java SE 8中我們可以直接把該靜態(tài)方法定義在Comparator接口中:
比如說下面的代碼:
冗余代碼實(shí)在太多了!
有了lambda表達(dá)式,我們可以去掉冗余的匿名類:
盡管代碼簡(jiǎn)潔了很多,但它的抽象程度依然很差:開發(fā)者仍然需要進(jìn)行實(shí)際的比較操作(而且如果比較的值是原始類型那么情況會(huì)更糟),所以我們要借助Comparator里的comparing方法實(shí)現(xiàn)比較操作:
在類型推導(dǎo)和靜態(tài)導(dǎo)入的幫助下,我們可以進(jìn)一步簡(jiǎn)化上面的代碼:
我們注意到這里的lambda表達(dá)式實(shí)際上是getLastName的代理(forwarder),于是我們可以用方法引用代替它:
最后,使用Collections.sort這樣的輔助方法并不是一個(gè)好主意:它不但使代碼變的冗余,也無法為實(shí)現(xiàn)List接口的數(shù)據(jù)結(jié)構(gòu)提供特定(specialized)的高效實(shí)現(xiàn),而且由于Collections.sort方法不屬于List接口,用戶在閱讀List接口的文檔時(shí)不會(huì)察覺在另外的Collections類中還有一個(gè)針對(duì)List接口的排序(sort())方法。
默認(rèn)方法可以有效的解決這個(gè)問題,我們?yōu)長(zhǎng)ist增加默認(rèn)方法sort(),然后就可以這樣調(diào)用:
此外,如果我們?yōu)镃omparator接口增加一個(gè)默認(rèn)方法reversed()(產(chǎn)生一個(gè)逆序比較器),我們就可以非常容易的在前面代碼的基礎(chǔ)上實(shí)現(xiàn)降序排序。
Lambda(類庫篇——Streams API,Collector和并行)
對(duì)于?anyMatch(Predicate)?和?findFirst()?這些急性求值操作,我們可以使用短路(short-circuiting)來終止不必要的運(yùn)算。以下面的流水線為例:
盡管并行是顯式的,但它并不需要成為侵入式的。利用?parallelStream()?,我們可以輕松的把之前重量求和的代碼并行化:
下面的代碼源自JDK中的?Class?類型(?getEnclosingMethod?方法),這段代碼會(huì)遍歷所有聲明的方法,然后根據(jù)方法名稱、返回類型以及參數(shù)的數(shù)量和類型進(jìn)行匹配:
通過使用流,我們不但可以消除上面代碼里面所有的臨時(shí)變量,還可以把控制邏輯交給類庫處理。通過反射得到方法列表之后,我們利用?Arrays.stream?將它轉(zhuǎn)化為?Stream?,然后利用一系列過濾器去除類型不符、參數(shù)不符以及返回值不符的方法,然后通過調(diào)用?findFirst?得到?Optional<Method>?,最后利用?orElseThrow?返回目標(biāo)值或者拋出異常。
相對(duì)于未使用流的代碼,這段代碼更加緊湊,可讀性更好,也不容易出錯(cuò)。
流操作特別適合對(duì)集合進(jìn)行查詢操作。假設(shè)有一個(gè)"音樂庫"應(yīng)用,這個(gè)應(yīng)用里每個(gè)庫都有一個(gè)專輯列表,每張專輯都有其名稱和音軌列表,每首音軌表都有名稱、藝術(shù)家和評(píng)分。
假設(shè)我們需要得到一個(gè)按名字排序的專輯列表,專輯列表里面的每張專輯都至少包含一首四星及四星以上的音軌,為了構(gòu)建這個(gè)專輯列表,我們可以這么寫:
我們可以用流操作來完成上面代碼中的三個(gè)主要步驟——識(shí)別一張專輯是否包含一首評(píng)分大于等于四星的音軌(使用?anyMatch?);按名字排序;以及把滿足條件的專輯放在一個(gè)List?中:
可變的集合操作(Mutative collection operation)
集合上的流操作一般會(huì)生成一個(gè)新的值或集合。不過有時(shí)我們希望就地修改集合,所以我們?yōu)榧?#xff08;例如?Collection?,?List?和?Map?)提供了一些新的方法,比如Iterable.forEach(Consumer)?,?Collection.removeAll(Predicate)?,?List.replaceAll(UnaryOperator)?,?List.sort(Comparator)?和?Map.computeIfAbsent()。除此之外,?ConcurrentMap?中的一些非原子方法(例如?replace?和?putIfAbsent)被提升到?Map?之中。
?
函數(shù)式編程簡(jiǎn)介
如果你不知道什么是函數(shù)式編程,或者不了解map,filter,reduce這些常用的高階函數(shù)。下文是簡(jiǎn)單介紹。或者找專業(yè)資料查閱。
高階函數(shù):一個(gè)函數(shù)就接收另一個(gè)函數(shù)作為參數(shù),這種函數(shù)就稱之為高階函數(shù)
1.高階函數(shù)之map:
此時(shí)我們有一個(gè)數(shù)組和一個(gè)接受一個(gè)參數(shù)并返回一個(gè)數(shù)的函數(shù)。我們需要把這個(gè)數(shù)組的每一個(gè)值在這個(gè)函數(shù)上走一遍,從而得到一個(gè)新數(shù)組。此時(shí)就需要map了
2.高階函數(shù)之reduce:
此時(shí)我們有一個(gè)數(shù)組和一個(gè)接受兩個(gè)參數(shù)并返回一個(gè)數(shù)的函數(shù)。我們需要把這個(gè)數(shù)組的每?jī)蓚€(gè)值在這個(gè)函數(shù)上走一遍變成一個(gè)值,然后再讓這個(gè)值繼續(xù)和下一個(gè)值走這個(gè)函數(shù),最后從而得到一個(gè)值。
3.高階函數(shù)之filter:
????此時(shí)我們有一個(gè)數(shù)組,這個(gè)數(shù)組里面有我們想要的也有我們不想要的,怎么辦,我們可以下一個(gè)函數(shù),讓這些值在這個(gè)函數(shù)里面走一遍,想要的留下,不想要的去掉,返回一個(gè)只有理想數(shù)值的數(shù)組。此時(shí)需要filter
4.高階函數(shù)之sort:
????這個(gè)就是之前數(shù)組里面提到的排序函數(shù),這個(gè)也是一個(gè)高級(jí)函數(shù),默認(rèn)是從低到高。?通常規(guī)定,對(duì)于兩個(gè)元素x和y,如果認(rèn)為x < y,則返回-1,如果認(rèn)為x == y,則返回0,如果認(rèn)為x > y,則返回1,這樣,排序算法就不用關(guān)心具體的比較過程,而是根據(jù)比較結(jié)果直接排序。我們可以傳入一個(gè)函數(shù),讓sort從高到低排序
?
from:?https://www.cnblogs.com/jiumao/p/7136369.html?
總結(jié)
- 上一篇: Java中的10颗语法糖
- 下一篇: Java8 Lambda不仅仅只是语法糖