Stream
Java 8 中Stream API詳解
Stream的作用
- Java8 中的Stream是對集合對象功能的增強,專注於對集合對象進行各種非常便利、高效的聚合操作,或者大批量數據操作
- Stream API藉助於同樣新出的Lambda表達式,極大的提高了編程效率和程序的可讀性
- 同時提供串行和並行兩種模式進行匯聚操作,併發模式可以充分利用多核處理器的優勢,使用fork/join並行方式來拆分任務和加速處理過程。
- 無需編寫一行多線程代碼就可以方便的寫出高性能的併發程序
Stream的性質
- 流(Stream):就是數據渠道,用於操作數據源(集合、數組)所生成的元素序列
- 集合注重的數據,流注重與計算
- Stream不會自己存儲數據
- Stream不會改變源對象。相反,他們會返回一個持有結果的新Stream
- Stream操作是延遲執行的。這意味着會等到需要結果的時候才執行。
什麼是聚合操作
在傳統J2EE的應用中,java代碼經常不得不依賴於關係型數據庫的聚合操作來完成諸如:
- 每日平均消費金額
- 最受歡迎的商品
- 取一定量的數據樣本作爲首頁推薦
這類的操作。而在java集合API中,僅僅有極少量的輔助型方法,更多的時候需要用Iterator來遍歷集合,完成相關的聚合應用邏輯。
舉例:
獲取數組最大值
public static void main(String[] args) {
int[] nums={1,2,3,4,5,6,7};
int max = Arrays.stream(nums).max().getAsInt();
System.out.println(max);
}
Stream總覽
什麼是流
Stream不是集合元素,不是數據結構並不保存數據,是有關算法和計算的,類似於一個高版本的Iterator。我們只需要給出需要對其包含的元素執行什麼操作,Stream就會隱式地在內部進行遍歷,做出相應的數據轉換。
Stream如同一個迭代器,單向,不可往復,數據只能遍歷一次,遍歷一次後即用盡了。
Stream可以並行化操作,迭代器只能命令式地、串行化操作。
Stream的一大特點就是數據源本身可以是無限的。
流的構成
分爲三個基本步驟
獲取一個數據源——數據轉化——執行操作獲取先要的結構
每次轉換原有的Stream對象不改變,返回一個新的Stream對象(可以有多次轉化),這就允許對其操作可以向鏈條一樣排列,變成一個管道
有多種方式生成Stream Source:
- 從Collection和數組
- Collection.stream()
- Collection.oarallelStream()
- Arrays.stream(T array)or Strean.of()
- 從BufferReader
- java.io.BUfferedReader.lines()
- 靜態工程
- 自己創建
流的操作類型
- Intermediate:其主要目的是打開流,做出某種程度的數據映射/過濾,然後返回一個新的流,交給下一個操作使用。僅僅調用到這類方法,並沒有真正開始流的遍歷
- Terminal:一個流只能有一個terminal操作,其操作的執行纔是真正開始流的遍歷,並生成一個結果
流的使用詳解
流的構造與轉換
- 構造流的幾種常見方法
public static void main(String[] args) {
// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "B", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();
}
- 數值流的構造
IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
IntStream.range(1, 3).forEach(System.out::println);
IntStream.rangeClosed(1, 3).forEach(System.out::println);
- 流轉換爲其他數據結構
// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();
流的操作
- 多箇中間操作可以連接起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何處理,而在終止操作時一次性全部處理。即惰性求值。Stream的中間操作時不會有任何結果數據輸出的。
- Stream的中間操作可以在整體上分爲:篩選與切片、映射、排序
篩選與切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda表達式,從流中排除某些元素 |
distinct() | 篩選,通過流所生成元素的hashCode()和equals()去除重複元素 |
limit(long maxSize) | 截斷流,使其元素不能超過給定數量 |
skip(long n) | 跳過元素,返回一個扔掉了前n個元素的流。若流中的元素不足n個,則返回一個空流與limit(n)互補 |
示例
- Employee類
public class Employee implements Serializable {
private String name;
private Integer age;
private Double salary;
}
- 構件對象數組
protected List<Employee> list = Arrays.asList(
new Employee("張三", 18, 9999.99),
new Employee("李四", 38, 5555.55),
new Employee("王五", 60, 6666.66),
new Employee("趙六", 8, 7777.77),
new Employee("田七", 58, 3333.33)
);
- Filter()方法
主要是接收Lambda表達式,從流中排除某些元素
//內部迭代:在此過程中沒有進行過迭代,由Stream api進行迭代
//中間操作:不會執行任何操作
Stream<Person> stream = list.stream().filter((e) -> {
System.out.println("Stream API 中間操作");
return e.getAge() > 30;
});
- limit()方法
主要作用爲:截斷流,使其元素不超過給定數量
//過濾之後取2個值
list.stream().filter((e) -> e.getAge() >30 ).limit(2).forEach(System.out :: println);
在其中我們可以配合其他的中間操作,並截斷流,使我們可以取得相應個數的元素。
- skip()方法
跳過元素,返回一個扔掉了前n個元素的流。若流中元素不足n個,則返回一個空流。與limit(n)互補
//跳過前2個值
list.stream().skip(2).forEach(System.out :: println);
- distinct()方法
篩選,通過流所生成元素的hashCode和equals()去除重複元素
list.stream().distinct().forEach(System.out :: println);
distict需要在實體中重寫hashCode和equal方法才能使用
映射
方法 | 描述 |
---|---|
map(Function f) | 接收一個函數,該函數會被引用到每個元素上,並將其映射成一個新的元素 |
mapToDouble(ToDoubleFunction f) | 接收一個函數,該函數會被引用到每個元素上,產生一個新的DoubleStream |
mapToInt(ToIntFunction f) | 接收一個函數,該函數會被引用到每個元素上,產生一個新的IntStream |
mapToLong(ToLongFunction f) | 接收一個函數,該函數會被引用到每個元素上,產生一個新的LongStream |
flagMap(function f) | 接收一個函數,將流中的每個值都換成另一個流,然後把所有流,連接成一個流。 |
示例
-
map() 方法
//將流中每一個元素都映射到map的函數中,每個元素執行這個函數,再返回 List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd"); list.stream().map((e) -> e.toUpperCase()).forEach(System.out::printf); //獲取Person中的每一個人得名字name,再返回一個集合 List<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toList());
-
flagMap()
/** * flatMap —— 接收一個函數作爲參數,將流中的每個值都換成一個流,然後把所有流連接成一個流 */ @Test public void testFlatMap () { StreamAPI_Test s = new StreamAPI_Test(); List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd"); list.stream().flatMap((e) -> s.filterCharacter(e)).forEach(System.out::println); //如果使用map則需要這樣寫 list.stream().map((e) -> s.filterCharacter(e)).forEach((e) -> { e.forEach(System.out::println); }); } /** * 將一個字符串轉換爲流 */ public Stream<Character> filterCharacter(String str){ List<Character> list = new ArrayList<>(); for (Character ch : str.toCharArray()) { list.add(ch); } return list.stream(); }
其實map方法相當於Collection的add方法,如果add的是個集合的話會變爲二維數組,而flatMap相當於Collection的addAll犯法
排序
方法 | 描述 |
---|---|
sorted() | 產生一個新流,其按自然順序排序 |
sort(Comparator comp) | 產生一個新流,其中按比較器書序排序 |
// 自然排序
List<Employee> persons = list.stream().sorted().collect(Collectors.toList());
//定製排序
List<Employee> persons1 = list.stream().sorted((e1, e2) -> {
if (e1.getAge() == e2.getAge()) {
return 0;
} else if (e1.getAge() > e2.getAge()) {
return 1;
} else {
return -1;
}
}).collect(Collectors.toList());