這次我參加2014 Java Developer Day 「解析JDK8 Functional API」主題演講筆記,這場演講是由良葛格所主講,原始簡報連結 http://www.slideshare.net/JustinSDK/jdk8-functional-api,文章當中的程式碼全部取自於該簡報。

Lambda API Refactor

下列範例是良葛格採用程式人必讀的經典名作"Refactoring"作為範例去對既有的程式以Lambda去做重構。Lambda Expression可以簡化程式碼,讓語法更簡潔,程式簡單寫,把更多工作交給Compiler去幫你做掉(見下圖)。

圖片來源:http://www.codeproject.com/KB/cs/507985/lambda.png

首先,我們要寫一支程式可以找所有唱片當中,曲目長度超過60,然後把曲目名稱加到集合當中回傳:

public Set<String> findLongTracks(List<Album> albums) {
    Set<String> trackNames = new HashSet<String>();
    for (Album album : albums) {
        for (Track track : album.getTrackList()) {
            if (track.getLength() > 60) {
                String name = track.getName();
                trackNames.add(name);
            }
        }
    }
    return trackNames;
}

第一次重構

我們針對取出曲目的loop寫另一個方法來實作:

public Set<String> longTrackNames(List<Track> tracks) {
    Set<String> trackNames = new HashSet<String>();
    for (Track track : tracks) {
        if (track.getLength() > 60) {
            String name = track.getName();
            trackNames.add(name);
        }
    }
    return trackNames;
}

原本的程式就可以使用該方法進行重構:

public Set<String> findLongTracks(List<Album> albums) {
    Set<String> trackNames = new HashSet<String>();
    for (Album album : albums) {
        List<Track> tracks = album.getTrackList(); 
        trackNames.addAll(longTrackNames(tracks)); // 重構方法

    }
}

第二次重構

我們把第一次重構所寫的longTrackNames()方法做第二次重構,這次我們把曲目長度超過60的if區塊取到另一個方法:tracksOverOneMin()

private List<Track> tracksOverOneMin(List<Track> tracks) {
    List<Track> tracksOverOneMin = new ArrayList<Track>();
    for (Track track : tracks) {
        if (track.getLength() > 60) {
            tracksOverOneMin.add(track);
        }
    }
    return tracksOverOneMin;
}

然後,longTrackNames()就可以重構如下:

public Set<String> longTrackNames(List<Track> tracks) {
    tracks = tracksOverOneMin(tracks); // 重構方法

    Set<String> trackNames = new HashSet<String>();
    for (Track track : tracks) {
        String name = track.getName();
        trackNames.add(name);
    }
    return trackNames;
}

第三次重構

這次重構,我們針對longTrackNames()裡面的取得曲目名稱流程做重構,另寫一個方法來取得曲目名稱:

private Set<String> collectNames(List<Track> tracks) {
    Set<String> trackNames = new HashSet<String>();
    for (Track track : tracks) {
        String name = track.getName();
        trackNames.add(name);
    }
    return trackNames;
}

接著,longTrackNames()就可以使用此方法來取得曲目名稱,使得整體方法變得更簡潔:

public Set<String> longTrackNames(List<Track> tracks) {
    tracks = tracksOverOneMin(tracks);
    return collectNames(tracks); // 重構方法

}

最後使用Lambda來重構

public Set<String> longTrackNames(List<Track> tracks) {
    tracks = tracks.stream()
                    .filter(track -> track.getLength() > 60)
                    .collect(toList());
    return tracks.stream()
                .map(track -> track.getName())
                .collect(toSet());
}

最後兩行程式碼重構合併成一行:

public Set<String> longTrackNames(List<Track> tracks) {
    return tracks.stream()
                .filter(track -> track.getLength() > 60)
                .map(track -> track.getNames())
                .collect(toSet());
}

總結

最後使用Lambda重構成一行的程式碼依照語意來說可以解讀成「把資料讀入,依照某個條件過濾資料,符合的資料將所要的資料欄位取出加入到新集合」,迴圈的操作經過Lambda重構後可以忽略不寫,整體程式碼抽象程度提高了。
Java Stream API的概念就像是*nux的pipeline,可以把資料用串流的方式讀入後,串接多個有意義的操作(例如:依條件過濾filter(Predicate)、取出對應值map(Function)、輸出結果集合collect(Collector)),最後輸出結果。 Java Stream API把很多for迴圈重構掉,我們只需要專注於每個操作的重點在哪,而不是迴圈如何寫、如何執行,如此可以提高整體程式碼的抽象程度,也同時提升整體程式的可閱讀性。

Comments

comments powered by Disqus