Stream 是用函數式編程方式在集合類上進行復雜操作的工具,其集成了Java 8中的衆多新特性之一的聚合操作,開發者可以更容易地使用Lambda表達式,並且更方便地實現對集合的查找、遍歷、過濾以及常見計算等。
Stream的操作分類
Stream的操作有Intermediate、Terminal和Short-circuiting:
-
Intermediate:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 skip、 parallel、 sequential、 unordered、concat,limit
-
Terminal:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、iterator
-
Short-circuiting:
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
Intermediate
Intermediate主要是用來對Stream做出相應轉換及限制流,其是一箇中間操作,會使用惰性求值,不會說一個Intermediate操作就會掃描流中所有的數據,而是在有Terminal時,會將前面的Intermediate收集起來,然後一起進行數據的分析。
使用Intermediate實際上是將源Stream轉換爲一個新的Stream,在新生成的Stream中仍然可以繼續進行Intermediate的操作,但是對於Terminal來說只能有一個。
map
map方法將對於Stream中包含的元素使用給定的轉換函數進行轉換操作,新生成的Stream只包含轉換生成的元素。爲了提高處理效率,官方已封裝好了,三種變形:mapToDouble,mapToInt,mapToLong。如果我們知道要將數據轉化爲int,long和double時儘量使用這三種方法,不要讓虛擬機去自己推斷,畢竟咱們的心思不告訴它,它會花費時間去思考的,這個就會有一定的效率問題。mapToDouble,mapToInt,mapToLong,這三種變形轉換之後是基本類型,double,int和long,而不是包裝類型。
點開Stream的map方法,如下:
/**
* Returns a stream consisting of the results of applying the given
* function to the elements of this stream.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param <R> The element type of the new stream
* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function to apply to each element
* @return the new stream
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
知道了其是使用Function函數式接口來進行的,Function接口有一個apply方法,傳入一個數,經過操作之後,返回一個值,不懂得自行百度。
所以我們明白了map的用法,接收一個值(這個值就是流中值),經過一系列的操作,返回我們需要的值。
現在提一個簡單的需求,將字符串轉換爲數字並打印出來,使用Stream的map的寫法:
public class Convert{
public static void main(String[] args) {
Stream.of("12","13","1","34","34","67").map(Integer::parseInt)//這裏可以使用mapToInt
.forEach(System.out::println); //forEach爲將轉換好的流進行打印
}
}
filter
filter方法對原Stream按照指定條件過濾,在新建的Stream中,只包含滿足條件的元素,將不滿足條件的元素過濾掉。
/**
* Returns a stream consisting of the elements of this stream that match
* the given predicate.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* predicate to apply to each element to determine if it
* should be included
* @return the new stream
*/
Stream<T> filter(Predicate<? super T> predicate);
從上面的代碼可以知道,filter使用需要傳遞Predicate函數式接口,這個接口也是JDK8提供的最用函數式接口,這個接口需要傳入一個數,然後返回一個布爾值,filter函數也是根據這個返回值來進行過濾的,如果值爲false則過濾掉。
現在提一個簡單的需求,將字符串轉換爲數字,並且只保留偶數的,最後打印出來,使用Stream的filter的寫法:
public class Test02 {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67").mapToInt(x -> Integer.parseInt(x))//這個map的寫法和那個一樣,只不過上面那種更加簡潔
.filter(x -> x % 2 == 0)//filter判斷的值要是布爾類型的
.forEach(x -> System.out.println(x));
}
}
distinct
distinct方法以達到去除掉原Stream中重複的元素,生成的新Stream中沒有沒有重複的元素。distinct不用傳值參數,去除的重複元素是除第一個以外的所有重複的數,也就是說一個數組中有很多個4,那麼除了保留第一個4外,其他的均過濾掉,例如:有一串數字:10,3,5,6,8,10,5,8,1,10,去重之後的排列是10,3,5,6,8,1,10
/**
* Returns a stream consisting of the distinct elements of this stream.
*
* <p>This is a <a href="package-summary.html#StreamOps">stateful
* intermediate operation</a>.
*
* @return the new stream
*/
IntStream distinct();
現在提一個簡單的需求,將字符串轉換爲數字,去重之後保留偶數,最後打印出來,使用Stream的filter的寫法:
public class DistinctTest {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67").mapToInt(x -> Integer.parseInt(x))
.distinct()
.filter(x -> x % 2 == 0)
.forEach(x -> System.out.println(x));
}
}
sorted
sorted方法將對原Stream進行排序,返回一個有序列的新Stream。sorterd有兩種變體sorted(),sorted(Comparator),前者將默認使用Object.equals(Object)進行排序,而後者接受一個自定義排序規則函數(Comparator),可按照意願排序。
public class SortTest {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67").map(x -> Integer.parseInt(x))
.distinct()
.sorted(((x, y) -> y - x))
.forEach(x -> System.out.println(x));
}
}
peek
peek方法生成一個包含原Stream的所有元素的新Stream,同時會提供一個消費函數(Consumer實例),新Stream每個元素被消費的時候都會執行給定的消費函數,並且消費函數優先執行。
在Terminal操作執行時,Intermediate的操作纔會執行,對於peek來說,如果peek的操作無法與peek之後關於流的操作無法進行優化一次執行,那麼關於peek的操作就會先執行(不會說一個Intermediate操作就會掃描流中所有的數據,而是在有Terminal時,會將前面的Intermediate收集起來,然後一起進行數據的分析),例如:
public class PeekTest01 {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67").map(x -> Integer.parseInt(x))
.peek(x->System.out.println("輸出的結果爲"+x))
.distinct()
.sorted(((x, y) -> y - x))
.forEach(System.out::println);
}
}
//輸出結果爲
//輸出的結果爲12
//輸出的結果爲13
//輸出的結果爲1
//輸出的結果爲34
//輸出的結果爲34
//輸出的結果爲67
//67
//34
//13
//12
//1
示例2:
public class PeekTest02 {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67").map(x -> Integer.parseInt(x))
.sorted(((x, y) -> y - x))
.peek(x->System.out.println("輸出的結果爲"+x))
.distinct()
.forEach(System.out::println);
}
}
//輸出的結果爲67
//67
//輸出的結果爲34
//34
//輸出的結果爲34
//輸出的結果爲13
//13
//輸出的結果爲12
//12
//輸出的結果爲1
//1
skip
skip方法接受一個long類型的數值,將過濾掉原Stream中的前N個元素,返回剩下的元素所組成的新Stream。如果原Stream的元素個數大於N,將返回原Stream的後(原Stream長度-N)個元素所組成的新Stream;如果原Stream的元素個數小於或等於N,將返回一個空Stream。
public class SkipTest {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67","10","45").map(x -> Integer.parseInt(x))
.skip(3)
.forEach(System.out::println);
}
}
//輸出結果
//34
//34
//67
//10
//45
concat
concat方法將兩個Stream連接在一起,合成一個Stream。若兩個輸入的Stream都時排序的,則新Stream也是排序的;若輸入的Stream中任何一個是並行的,則新的Stream也是並行的;若關閉新的Stream時,原兩個輸入的Stream都將執行關閉處理。
public class ConcatTest03 {
public static void main(String[] args) {
Stream.concat(Stream.of(1, 2, 4), Stream.of(3, 5))
.forEach(integer -> System.out.print(integer + " "));
}
}
// 打印結果
// 1 2 4 3 5
limit
limit接收一個long類型的數組,返回一個限定大小的流,如果limit(n),只會選取前n個值,之後的值則會被拋棄。
public class LimitTest {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67","10","45").map(x -> Integer.parseInt(x))
.limit(4)
.forEach(x-> System.out.print(x+" "));
}
}
//輸出結果
//12 13 1 34