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函數語言程式設計》