1. 程式人生 > >Java8函數語言程式設計1-簡介和流

Java8函數語言程式設計1-簡介和流

一、什麼是函數語言程式設計

函數語言程式設計的核心是:在思考問題時,使用不可變值和函式,函式對一個值進行處理,對映成另一個值。
為支援函數語言程式設計,Java8 引入了Lambda表示式。我對Java8中的Lambda表示式的理解是,使用匿名函式替換Java中的樣本程式碼(匿名內部類)。

Runnable noArguments = () -> System.out.println("Hello World");
BinaryOperator<Long> add = (x, y) -> x + y;
BinaryOperator<Long> addExplicit
= (Long x, Long y) ->
x + y;

Lambda表示式的型別依賴於上下文環境,是由編譯器推斷出來的。在引用外部變數時,不管是否宣告為final(Java8取消了必須宣告為final的限制),這個變數還是要是終態的,否則編譯器會報錯(local variables referenced from a Lambda expression must be final or effectively final)。也就是說,Lambda表示式在引用外部變數時引用的是值,而不是變數。

二、流(Stream)

1、內部迭代與外部迭代

在操作集合類時,我們通常需要編寫大量程式碼。比如在計算來自倫敦的藝術家人數時,我們通常的寫法是

int count = 0;
for (Artist artist : allArtists) {
    if (artist.isFrom("London")) {
        count++;
    }
}

這樣的迭代叫做外部迭代,外部迭代的實際原理是獲取集合的迭代器Iterator,使用Iterator來控制迭代過程。而內部迭代的含義是迭代在集合內部控制下進行。Stream(流)是用函數語言程式設計方法在集合類上進行復雜操作的工具,使用Stream可以將上面的迭代改寫為:

long count = allArtists.stream()
                       .filter
(artist -> artist.isFrom("London")) .count();

在這種方法中,filter只是刻畫描述了stream,並沒有產生新的集合,它的返回值還是stream,這稱為惰性求值方法,而count這樣從stream產生值的方法叫做及早求值方法,這樣的方法返回值一般為空或另一個值。

2、常用的流操作

(1)collect(toList())

collect(toList())方法由Stream裡的值生成一個列表,是一個及早求職方法。

List<String> collected = Stream.of("a","b","c")
                               .collect(Collectors.toList());

(2)map

如果有一個函式可以將一種型別的值轉換成另一種型別,map操作就可以使用該函式,將一個流中的值轉換成一個新的流。

List<String> collected = Stream.of("a", "b", "hello")
                               .map(string -> string.toUpperCase()) 
                               .collect(toList());
assertEquals(asList("A", "B", "HELLO"), collected);

傳給map的lambda表示式必須是實現Function介面的一個例項。

(3)filter

關於filter上面已經給過示例,很多情況下我們需要在列表中選出符合條件的某幾項,這一類程式碼稱為filter模式,其核心思想是保留stream中的一些元素而過濾掉其他的元素。
傳給filter的lambda表示式必須是實現Predicate介面的一個例項。

(4)max和min

List<Track> tracks = asList(new Track("Bakai", 524),
                            new Track("Violets for Your Furs", 378),
                            new Track("Time Was", 451));
Track shortestTrack = tracks.stream()
                            .min(Comparator.comparing(track->track.getLength()))
                            .get();

min或max的輸入引數是一個Comparator物件,Java8提供了新的方法comparing,我們需要提供需要比較內容的存取方法就可以實現比較器。

(5)reduce

reduce操作可以從一組值中生成一個值。上面示例的count、min、max其實都是reduce操作。

int count = Stream.of(1,2,3)
                  .reduce(0, (acc,element) -> acc + element);

第一個引數0為初始值,第二個引數lambda表示式是一個BinaryOperator例項。

3、多次呼叫流操作

儘量使用流的鏈式呼叫,而不是每一步強制對函式求值。
錯誤使用流的例子:

List<Artist> musicians = album.getMusicians()
                              .collect(toList());
List<Artist> bands = musicians.stream()
                              .filter(artist -> artist.getName().startsWith("The"))
                              .collect(toList());
Set<String> origins = bands.stream()
                           .map(artist -> artist.getNationality())
                           .collect(toSet());

正確的寫法應該是:

Set<String> origins = album.getMusicians()
                      .filter(artist -> artist.getName().startsWith("The"))
                      .map(artist -> artist.getNationality())
                      .collect(toSet());

使用鏈式呼叫可以提高可讀性和效率,便於自動並行化處理。

參考並建議閱讀:《Java 8函數語言程式設計》