首先看一個問題:在這個task集合中一共有多少個OPEN狀態的?計算出它們的points屬性和。在Java 8之前,要解決這個問題,則需要使用foreach循環遍歷task集合;但是在Java 8中可以利用steams解決:包括一系列元素的列表,並且支持順序和並行處理。
// Calculate total points of all active tasks using sum() final long totalPointsOfOpenTasks = tasks .stream() .filter( task -> task.getStatus() == Status.OPEN ) .mapToInt( Task::getPoints ) .sum(); System.out.println( "Total points: " + totalPointsOfOpenTasks );
這里有很多知識點值得說。首先,tasks
集合被轉換成steam
表示;其次,在steam
上的filter
操作會過濾掉所有CLOSED
的task
;第三,mapToInt
操作基於tasks
集合中的每個task
實例的Task::getPoints
方法將task
流轉換成Integer
集合;最后,通過sum
方法計算總和,得出最后的結果。
在學習下一個例子之前,還需要記住一些steams(點此更多細節)的知識點。Steam之上的操作可分為中間操作和晚期操作。
中間操作會返回一個新的steam——執行一個中間操作(例如filter)並不會執行實際的過濾操作,而是創建一個新的steam,並將原steam中符合條件的元素放入新創建的steam。
晚期操作(例如forEach或者sum),會遍歷steam並得出結果或者附帶結果;在執行晚期操作之后,steam處理線已經處理完畢,就不能使用了。在幾乎所有情況下,晚期操作都是立刻對steam進行遍歷。
steam的另一個價值是創造性地支持並行處理(parallel processing)。對於上述的tasks集合,我們可以用下面的代碼計算所有task的points之和:
// Calculate total points of all tasks final double totalPoints = tasks .stream() .parallel() .map( task -> task.getPoints() ) // or map( Task::getPoints ) .reduce( 0, Integer::sum ); System.out.println( "Total points (all tasks): " + totalPoints );
這里我們使用parallel方法並行處理所有的task,並使用reduce方法計算最終的結果。控制台輸出如下:
對於一個集合,經常需要根據某些條件對其中的元素分組。利用steam提供的API可以很快完成這類任務,代碼如下:
// Group tasks by their status final Map< Status, List< Task > > map = tasks .stream() .collect( Collectors.groupingBy( Task::getStatus ) ); System.out.println( map );
控制台的輸出如下:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
最后一個關於tasks集合的例子問題是:如何計算集合中每個任務的點數在集合中所占的比重,具體處理的代碼如下:
// Calculate the weight of each tasks (as percent of total points) final Collection< String > result = tasks .stream() // Stream< String > .mapToInt( Task::getPoints ) // IntStream .asLongStream() // LongStream .mapToDouble( points -> points / totalPoints ) // DoubleStream .boxed() // Stream< Double > .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream .mapToObj( percentage -> percentage + "%" ) // Stream< String> .collect( Collectors.toList() ); // List< String > System.out.println( result );
控制台輸出結果如下:
[19%, 50%, 30%]
最后,正如之前所說,Steam API不僅可以作用於Java集合,傳統的IO操作(從文件或者網絡一行一行得讀取數據)可以受益於steam處理,這里有一個小例子:
final Path path = new File( filename ).toPath(); try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) { lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println ); }
Stream的方法onClose()
返回一個等價的有額外句柄的Stream,當Stream的close()
方法被調用的時候這個句柄會被執行。Stream API、Lambda表達式還有接口默認方法和靜態方法支持的方法引用,是Java 8對軟件開發的現代范式的響應。