此次主要介紹Java 8的Stream以及如何與lambda配合使用。Stream做爲Java 8的一大亮點,它與java.io包裏的InputStream和OutputStream是徹底不一樣的概念。Java 8中的Stream是對集合對象功能的加強,它專一於對集合對象進行各類很是便利、高效的聚合操做,或者大批量數據操做。Stream API藉助於一樣新出現的lambda表達式,極大的提升編程效率和程序可讀性。能夠說,Stream的出現,徹底改變了處理集合的方式。但願你們在看完這篇文章後,能拋棄以前對集合用Iterator遍歷並完成相關的聚合操做那種笨拙的方式,改用流來處理。
先用一個例子讓你們感覺一下Stream的便捷。假設有這樣一個Book類:java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
private Integer id;
private String name;
private String type;
private Double price;
}
複製代碼
如今有這樣一個業務場景:要發現種類爲「計算機」的全部圖書,而後返回以價格增序排序好的圖書ID集合:
傳統方式算法
public class StreamTest {
public static void main(String[] args) {
List<Book> bookList = new ArrayList<>();
bookList.addAll(Arrays.asList(new Book(1, "Java核心技術", "計算機", 90.0),
new Book(2, "Java編程思想", "計算機", 100.0),
new Book(3, "浮生六記", "文學", 50.0)));
List<Book> computerBooks = new ArrayList<>();
for (Book b : bookList) {
if (b.getType().equals("計算機")) {
computerBooks.add(b);
}
}
Collections.sort(computerBooks, new Comparator<Book>() {
@Override
public int compare(Book b1, Book b2) {
return b1.getPrice().compareTo(b2.getPrice());
}
});
List<Integer> bookIds = new ArrayList<>();
for (Book b : computerBooks) {
bookIds.add(b.getId());
}
}
}
複製代碼
使用Stream編程
public class StreamTest {
public static void main(String[] args) {
List<Book> bookList = new ArrayList<>();
bookList.addAll(Arrays.asList(new Book(1, "Java核心技術", "計算機", 90.0),
new Book(2, "Java編程思想", "計算機", 100.0),
new Book(3, "浮生六記", "文學", 50.0)));
List<Integer> bookIds = bookList.stream()
.filter(b -> b.getType().equals("計算機"))
.sorted(Comparator.comparing(Book::getPrice))
.map(Book::getId)
.collect(Collectors.toList());
}
}
複製代碼
能夠看到,原先繁瑣的操做,在使用了Stream後,只用一句話就解決了。那Stream到底是什麼呢?數據結構
Stream不是集合元素或者數據結構,它並不保存數據,而是有關數據的算法和計算的。能夠把Stream理解成一個高級版本的Iterator。原始版本的Iterator,用戶只能顯式地一個一個遍歷元素並對其執行某些操做;高級版本的Stream,用戶只要給出須要對其包含的元素執行什麼操做,好比 「過濾掉長度大於10的字符串」、「獲取每一個字符串的首字母」等,Stream會隱式地在內部進行遍歷,作出相應的數據轉換(ps:也能夠把Stream理解成一種處理數據的風格,這種風格將要處理的元素集合看做一種流,流在管道中傳輸,而且能夠在管道的節點上進行處理,好比篩選、排序、聚合等)。
可能上面說的有些抽象,下面給出一個具體的例子。有這樣一段代碼:多線程
List<Integer> nums = new ArrayList<>();
nums.addAll(Arrays.asList(1, null, 3, 4, null, 6));
nums.stream().filter(num -> num != null)).count();
複製代碼
上面這段代碼的目的是獲取一個List中不爲null的元素的個數。經過這段代碼,咱們來剖析一下Stream的結構: 併發
上圖中三個方框,也就是咱們使用Stream的三個基本步驟。紅色框中的語句負責建立一個Stream實例;綠色框中的語句對集合元素進行數據轉換,每次轉換原有Stream對象不改變,返回一個新的Stream對象(能夠有屢次轉換),這就容許對其操做能夠像鏈條同樣排列,變成一個管道;藍色框中的語句把Stream裏包含的內容按照某種算法來匯聚成一個值。再形象一點,就是這樣的: 須要強調的是,在對一個Stream進行屢次Intermediate操做時,每次都對Stream的每一個元素進行轉換,但實質上並無作N(轉換次數)次for循環。轉換操做都是lazy的,多個轉換操做只會在Terminal操做的時候融合起來,一次循環完成。咱們能夠這樣簡單的理解,Stream裏有個操做函數的集合,每次轉換操做就是把轉換函數放入這個集合中,在Terminal操做的時候循環Stream對應的集合,而後對每一個元素執行全部的函數。
至於有哪些轉換和聚合方法呢,你們能夠自行查找。推薦一篇文章:ifeve.com/stream/ ,裏面用示意圖說明了幾個典型的轉換和聚合方法,很形象。框架
map的做用是把input Stream的每個元素,映射成output Stream的另一個元素,例如:ide
// 大小寫轉換
List<String> wordList = Arrays.asList("a", "b", "c");
List<String> newWorldList = wordList.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
複製代碼
map生成的是1:1映射,每一個輸入元素,都按照規則轉換成爲另一個元素。還有一些場景,是一對多映射關係的,這時須要flatMap:函數
Stream<List<Integer>> inputStream = Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
);
Stream<Integer> outputStream = inputStream
.flatMap((childList) -> childList.stream());
複製代碼
flatMap把input Stream中的層級結構扁平化,就是將最底層元素抽出來放到一塊兒。最終output Stream裏面已經沒有List了,都是直接的數字。大數據
這個方法的主要做用是把Stream元素組合起來。它提供一個起始值,而後依照運算規則(BinaryOperator),和前面Stream的第一個、第二個、第n個元素組合。從這個意義上說,字符串拼接、數值的sum、min、max、average都是特殊的reduce。例如Stream的sum就至關於如下兩種寫法:
Integer sum = integers.reduce(0, (a, b) -> a+b);
Integer sum = integers.reduce(0, Integer::sum);
複製代碼
reduce的用例以下:
// 字符串鏈接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 無起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 過濾、字符串鏈接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F")
.filter(x -> x.compareTo("Z") > 0)
.reduce("", String::concat);
複製代碼
Stream有三個match方法,從語義上說:
它們都不是要遍歷所有元素才能返回結果。例如allMatch只要一個元素不知足條件,就skip剩下的全部元素,返回false。match的用例以下:
List<Person> persons = new ArrayList();
persons.add(new Person(1, "name" + 1, 10));
persons.add(new Person(2, "name" + 2, 21));
persons.add(new Person(3, "name" + 3, 34));
persons.add(new Person(4, "name" + 4, 6));
persons.add(new Person(5, "name" + 5, 55));
boolean isAllAdult = persons.stream()
.allMatch(p -> p.getAge() > 18);
System.out.println("All are adult? " + isAllAdult);
boolean isThereAnyChild = persons.stream()
.anyMatch(p -> p.getAge() < 12);
System.out.println("Any child? " + isThereAnyChild);
複製代碼
emmm就先寫這幾個吧,其實Stream另外一個便捷的地方在於它的一些方法能夠直接根據方法名來判斷用途。固然,想要掌握Stream的用法,仍是要——多用。
最後,說幾個lambda表達式須要注意的地方:
初識lambda表達式,可能有的人還會以爲很陌生,可是在掌握它的用法以後,必定能感覺到它的強大之處!