1. 程式人生 > >java函數語言程式設計:流

java函數語言程式設計:流

java8對核心類庫的改進主要包括集合類的API和新引入的流(Stream)
流使程式設計師得以站在更高層次上對集合進行操作

介紹Stream類中的一組方法,每個方法都對應集合上的一種操作

外部迭代->內部迭代

計算倫敦藝術家人數:

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

在集合上進行迭代,對每一個返回元素再進行處理

問題:
每次迭代集合時,都需要寫很多樣板程式碼,影響程式碼可讀性

for迴圈是一個封裝了迭代的語法糖,首先呼叫iterator方法產生一個新的Interator物件,
進而控制整個迭代過程,這就是外部迭代
迭代過程通過顯式呼叫Interator物件的hasNext和next方法完成迭代

int count = 0;
Interator<Artist> interator = allArtists.interator();
while(interator.hasNext()){
	Artist artist = interator.next();
	if(artist.isFrom("London")){
		count++;
    }
}

外部迭代的問題:
本質上是一種序列操作

使用for迴圈會將行為和方法混為一談

內部迭代:

另一種迭代方式就是內部迭代

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

stream()方法和interator()方法作用一樣,
該方法返回的並不是一個Interator物件,而是返回內部迭代中的相應介面Stream
Stream是用函數語言程式設計方式在集合類上進行復雜操作的工具

可被分解為兩步:
1,filter(artist -> artist.isFrom(“London”))找不所有來自倫敦的藝術家
2,count()計算他們的人數
每種操作都對應Stream介面的一個方法
找出來自倫敦的藝術家,需要對Stream物件進行過濾:filter
(過濾指:只保留通過某項測試的物件,測試由函式完成返回true或false)
count()方法計算給點Stream中包含的物件數量

實現機制

整個過程被分解為兩種簡單操作:過濾和計數
問題:兩種操作是否需要兩次迴圈?
事實上,返回的Stream物件不是一個新的集合,而是建立一個新集合的配方

惰性求值方法和早求值方法:
allArtists.stream()
			.filter(artist -> artist.isFrom("London"))

filter只描述了stream,沒有產生新集合,這種方法稱為惰性求值方法

而count方法最終會從Stream產生值,這種方法稱為及早求值方法

驗證惰性求值方法
long count = allArtists.stream()
					.filter(artist -> {
						System.out.println(artist.getName());
						return artist.isFrom("London")
					});

在過路器中加入一句列印輸出藝術家名字,執行程式碼不會輸出任何資訊

驗證及早求值方法:
long count = allArtists.stream()
					.filter(artist -> {
						System.out.println(artist.getName());
						return artist.isFrom("London")
					})
					.count();

在加入一個擁有終止操作的流,此時計數操作和藝術家名字都會被輸出

惰性求值方法和早求值方法的判斷

只需要看返回值即可,
若返回值是Stream就是惰性求值方法,
若返回值是一個值或空,就是及早求值方法

整個操作類似於建造者模式,使用一系列操作設定屬性和配置,最後呼叫build方法物件才被真正建立

常用流操作

1,collect(toList()):
由Stream裡的值生成一個列表,是一個及早求值操作

Stream的of方法使用一組初始值生成新的Stream

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

首先由列表生成一個Stream,然後進行Stream的collect操作,由Stream生成列表

2,map

使用for迴圈將字串轉換為大寫

List<String> collected = new ArrayList<>();
for(String string : asList("a", "b", "hello")){
	String uppercaseString = string.toUpperCase();
	collected.add(uppercaseString);
}

使用map操作將字元創轉換為大寫

List<String> collected = Stream.of("a", "b", "hello")
											.map(string -> string.toUpperCase())
											.collect(collectors.toList)

Lambda表示式必須是Function介面的一個例項,
Function介面是隻包含一個引數的普通函式介面,引數和返回值不必屬於同一型別

3,filter

遍歷資料並檢查元素時使用

使用for迴圈遍歷列表,使用條件語句做判斷:

List<String> beginningWithNumbers = new ArrayList<>();
for(String value : asList("a", "1abc", "abc1")){
	if(isDigit(value.charAt(0))){// 數字開頭
		beginningWithNumbers.add(value);
	}
}

使用函式式風格:

List<String> beginningWithNumbers = Stream.of("a", "1abc", "abc1")
															.filter(value -> isDigit(value.charAt(0)))
															.collect(toList())

for迴圈中的if是一個很強的訊號,可使用filter方法替代
和map相似,filter接受一個函式作為引數,該函式使用Lambda表示式表示,值為true的元素會被保留下來
該Lambda表示式的函式介面是Predicate介面(T->Predicate->boolean)

4,flatMap

將多個Stream連線成一個Stream

List<Integer> together = Stream.of(asList(1, 2), asList(3, 4))
										.flatMap(numbers -> numbers.stream())
										.collect(toList());

呼叫.stream()方法將每個列表轉換成Stream物件,其餘部分由flatMap方法處理
flatMap方法的函式介面也是Function,方法的返回值為Stream型別

5,max和min

在Stream上求最大值和最小值

使用for迴圈,根據曲目長度,查詢最短曲目

List<Track> tracks = asList(new Track("abc", 1000),
							new Track("abc", 500),
							new Track("abc", 700))
Track shortestTrack = tracks.get(0);
for(Track track : tracks){
	if(track.getLength() < shortestTrack){
		shortestTrack = track;
	}
}

min()根據曲目長度,查詢最短曲目

List<Track> tracks = asList(new Track("abc", 1000),
							new Track("abc", 500),
							new Track("abc", 700))
Track shortestTrack = tracks.stream()
									.min(Comparator.comparing(track -> track.getLength()))
									.get();

使用Comparator物件中java8新提供的靜態方法comparing,實現一個比較器
comparing方法接受一個函式並返回另一個函式

還可以呼叫空Stream的max方法,返回Optional物件
Optional物件表示一個可能存在也可能不存在的值
如果Stream為空,該值不存在,如果不為空,則該值存在
通過呼叫get方法可以取出Optional物件中的值

6,reduce
reduce操作可以實現從一組值中生成一個值
count,min,max都是reduce方法

reduce的累加過程:

每一步都將Stream中的元素進行累加,遍歷至Stream中的最後一個元素時,得到累加值

使用reduce求和:

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

傳入Stream的兩個引數分別是:當前元素和acc,
acc是累加器,儲存當前累加結果
Lambda表示式返回值為最新的acc,上一輪acc的值和當前元素的相加結果
reduce的型別是BinaryOperator

展開reduce操作:

BinaryOperator<Integer> accumulator = (acc, element) -> acc + element
int count = accumuator.apply(
				accumuator.apply(
					accumuator.apply(0, 1), 
				2), 
			3);

指令式程式設計求和:

int acc = 0;
for(Integer element : asList(1, 2, 3)){
	acc = acc = element;
}

對於集合來說,迴圈在外部,且需要手動更新