在上一篇文章中蹄皱,我們大致介紹了函數(shù)式編程的情況,包括函數(shù)式編程的緣起芯肤,意義巷折,還有高階函數(shù),柯里化等特點崖咨,今天我們將從lambda風(fēng)格锻拘,流操作,高階函數(shù)击蹲,并行化等幾個方面署拟,一起簡單聊聊函數(shù)式編程在Java8中的實現(xiàn)。
Lambda風(fēng)格
在上篇介紹函數(shù)式編程的文章中歌豺,我們大致了解了lambda表達式的來源以及定義推穷,那么lambda表達式在java8中是怎么使用的呢?
lambda表達式允許函數(shù)作為一個方法的參數(shù)类咧,在代碼的實現(xiàn)上顯得更加簡潔緊湊馒铃。
lambda表達式的格式語法如下
(parameters) -> expression
(parameters) -> {statement;}
在lambda表達式出現(xiàn)之前,如果想將代碼作為數(shù)據(jù)傳遞痕惋,一般會采用匿名類的方式区宇。比如在swing編程中,如果想在button上添加點擊操作對應(yīng)的事件監(jiān)聽器
botton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent event){
// Print something
}
}
)值戳;
如果使用lambda表達式议谷,可以一行代碼解決
botton.addActionListener(event -> System.out.println("something"));
使用匿名類需要顯式聲明參數(shù)類型ActionEvent,但是會使用lambda表達式無需指定類型堕虹,這是由于javac根據(jù)程序的上下文在后臺推斷出event的類型
流操作
流操作中包括惰性求值和及早求值兩種卧晓,一般來說,對于只描述stream,不產(chǎn)生新集合的方法我們稱為惰性求值赴捞;而對于最終會從stream產(chǎn)生值的方法逼裆,我們稱為及早求值。常見的惰性求值有map,reduce,filter等螟炫,及早求值包括count,sum波附,collect等艺晴。
下面昼钻,我們采用漸進式重構(gòu)的方式來舉例介紹常用的幾個流操作掸屡。
- 命令式編程
public Set<String> findLongTracks(List<Albulm>){
Set<String> trackNames = new HashSet<>();
for(Album album : albums) {
for (Track track : album.getTrackList()) { if (track.getLength() > 60) {
String name = track.getName();
trackNames.add(name);
}
}
}
return trackNames;
}
- stream化,并使用foreach進行重構(gòu)
public Set<String> findLongTracks(List<Albulm>){
Set<String> trackNames = new HashSet<>();
albums.stream().
forEach(album -> {
album.getTracks().forEach(track -> {
if (track.getLength() > 60) {
String name = track.getName();
trackNames.add(name);
}
);
}
);
return trackNames;
}
- 使用map,filter進行重構(gòu)
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames = new HashSet<>();
albums.stream()
.forEach(album -> {
album.getTracks()
.filter(track -> track.getLength() > 60)
.map(track -> track.getName())
.forEach(name -> trackNames.add(name));
});
return trackNames;
}
- 使用map,collect繼續(xù)進行重構(gòu)
public Set<String> findLongTracks(List<Album> albums) {
Set<String> trackNames = new HashSet<>();
albums.stream()
.map(album -> {
album.getTracks()
.filter(track -> track.getLength() > 60)
.map(track -> track.getName())
.forEach(name -> trackNames.add(name));
}).collect(Collecotor.toSet());
return trackNames;
}
- 使用flatMap進行重構(gòu)
public Set<String> findLongTracks(List<Album> albums) {
return albums.stream()
.flatMap(album -> album.getTracks())
.filter(track -> track.getLength() > 60)
.map(track -> track.getName())
.collect(toSet());
}
高階函數(shù)
高階函數(shù)是使用其他函數(shù)作為參數(shù)然评,并返回其他函數(shù)作為結(jié)果仅财。它可以幫助我們將創(chuàng)建和處理區(qū)分開來,通過這種方式碗淌,新的業(yè)務(wù)邏輯處理對象就可以輕易的添加進來盏求,而沒有必要同對象創(chuàng)建邏輯相耦合。
public interface Block<T> {
void apply(T t);
}
public void doWithContact(String fileName, Block<Contact> block) {
try {
String contacStr = FileUtils.readFileToString(new File(fileName));
Contact.apply(contact);
block.apply(contact);
}
catch (IOException e) {
System.out.println("cloudn't load contact file: " + e.getMessage());
}
catch (ParseException e) {
System.out.println("cloudn't parse contact file: " + e.getMessage());
}
}
//usage
doWithContact("custerX.vcf", c -> ContactDao.save(c))
并行化
并發(fā)(Concurrent)與并行(Parallel)
這是兩個比較容易混淆的概念亿眠。二者都可以表示兩個或者多個任務(wù)一起執(zhí)行碎罚,但是側(cè)重點不同;并發(fā)側(cè)重于多個任務(wù)交替執(zhí)行纳像,而多個任務(wù)之間有可能是串行的荆烈;并發(fā)是邏輯上的同時發(fā)生,而并行是物理上的同時發(fā)生竟趾。嚴(yán)格意義上憔购,并行的多個任務(wù)是真實同時執(zhí)行,而對于并發(fā)岔帽,則是交替的玫鸟,一會執(zhí)行任務(wù)A,一會執(zhí)行任務(wù)B犀勒,系統(tǒng)會不斷地在兩者之間進行切換屎飘,
并行化操作流
并行化操作流只需要改變一個方法的調(diào)用,如果已經(jīng)存在y一個stream對象贾费,調(diào)用它的parallel方法就能讓其立即擁有并行操作的能力枚碗。
public Set<String> findLongTracks(List<Album> albums) {
return albums.parallelStream()
.flatMap(album -> album.getTracks())
.filter(track -> track.getLength() > 60)
.map(track -> track.getName())
.collect(toSet());
}
parallelStream的底層與Fork/Join類似,默認(rèn)的并發(fā)程度是可用CPU數(shù)-1
在使用并行化編程時铸本,需要注意:
- 線程安全
- 合理參數(shù)配置:比如線程池的大小肮雨,等待隊列大小,并行度大小以及等待超時時間等等箱玷,都需要根據(jù)自己的業(yè)務(wù)不斷的調(diào)優(yōu)防止出現(xiàn)隊列不夠用或者超時時間不合理等