Java8 stream流的演示

前言

我們前面說了 lambda表達式,這次我們就將下JDK8的另一個新特性,流(Stream)

stream和lambda搭配使用效果更佳,(如果你沒有學過lambda表達式,最好先學習下lambda表達式)

看着逼格也更高,也更簡潔

我們就拿之前的lambda表達式的舉例

我們需要找出集合中所有的 男同學 按照年齡從小到大排序 並且打印出來,我們就這樣寫

studentList.stream()
                .filter(student -> "男".equals(student.getSex()))
                .sorted((x, y) -> x.getAge()-y.getAge())
                .forEach(student -> System.out.println(JSON.toJSONString(student, true)));

項目代碼在 github上 。jdk8 stream流的演示,有一個lambda_demo,找到測試的文件夾就行
(如果文章圖片失效也可以在上面看,文章更全更新更及時)

定義

Stream(流)是一個來自數據源元素隊列並支持聚合操作

  • 元素是特定類型的對象,形成一個隊列。 Java中的Stream並不會存儲元素,而是按需計算。
  • 數據源 流的來源。 可以是集合,數組,I/O channel, 產生器generator 等。
  • 聚合操作 類似SQL語句一樣的操作, 比如filter, map, reduce, find, match, sorted等。

流的處理流程一般是這樣

+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+       +------+   +------+   +---+   +-------+

聚合操作按照分法可以分成兩種:中間操作,終端操作

  • 中間操作
    • 無狀態操作
      • filter、map、peek等
    • 有狀態操作
      • Sorted、distinct、limit等
  • 終端操作
    • 非短路操作
      • foreach、count、collect等
    • 短路操作
      • findFirst、findAny、anyMatch等

中間操作

中間操作和很多命令像不像我們 sql 裏面的命令,你可以理解爲我們的那些限制語句,通過這些手段得到我們想要的一些數據

終端操作

顧名思義,就是指最後的操作。一個流裏面進行完終端操作之後就不能再進行其他操作了

無狀態操作

就是不需要全部遍歷完之後才能得到,比如 我上面的代碼,我只看這個元素符不符合,不符合我就不要,不需要遍歷完全部元素。與此相對,有狀態操作就是需要整個集合遍歷完纔行,比如我們的 sorted,我不遍歷完所有元素,我怎麼知道哪一個最大,哪一個最小

短路操作

就是找到一個我們就不往下執行了。與此相反,非短路操作也很好理解

各個方法演示

我的集合中有如下元素

private static List<Student> studentList = new ArrayList<Student>() {
        {
            add(new Student("張三丰", 20, "男", "體育",
                    180, 75, "太上老君"));
            add(new Student("張無忌", 18, "男", "語文",
                    178, 73, "文曲星"));
            add(new Student("趙敏", 17, "女", "數學",
                    170, 50, "太白金星"));
            add(new Student("金毛獅王", 25, "男", "體育",
                    176, 80, "太白金星"));
            add(new Student("周芷若", 16, "女", "語文",
                    168, 48, "太上老君"));
            add(new Student("張三", 21, "男", "英語",
                    172, 65, "如來"));
            add(new Student("趙勇", 26, "男", "體育",
                    188, 80, "太上老君"));


        }
    };

中間操作

無狀態操作

filter

filter,就是過濾掉那些不符合你設定的條件的元素

我們看源碼

/**
     * 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);

// 再看他的參數,記不記得我當初 講 lambda 時候講到的 這個
// Predicate 接口 是輸入一個類型,返回一個bool值

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}

所以我們使用的時候,返回的是一個bool值,如下所示

Equals 方法返回的是一個bool值

studentList.stream()
                .filter(student -> "男".equals(student.getSex()))
                .forEach(student -> System.out.println(JSON.toJSONString(student, true)));
    

結果,我們看到它裏面已經過濾掉了女同學

{
	"age":20,
	"height":180,
	"name":"張三丰",
	"sex":"男",
	"subject":"體育",
	"teacher":"太上老君",
	"weight":75
}
{
	"age":18,
	"height":178,
	"name":"張無忌",
	"sex":"男",
	"subject":"語文",
	"teacher":"文曲星",
	"weight":73
}
{
	"age":25,
	"height":176,
	"name":"金毛獅王",
	"sex":"男",
	"subject":"體育",
	"teacher":"太白金星",
	"weight":80
}
{
	"age":21,
	"height":172,
	"name":"張三",
	"sex":"男",
	"subject":"英語",
	"teacher":"如來",
	"weight":65
}
{
	"age":26,
	"height":188,
	"name":"趙勇",
	"sex":"男",
	"subject":"體育",
	"teacher":"太上老君",
	"weight":80
}


map

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>.
     * @return the new stream
     */
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);


 		// 他要傳入的是一個 Function 接口,作用是輸入一個類型,返回另一個類型
    @FunctionalInterface
    public interface Function<T, R> {

        /**
         * Applies this function to the given argument.
         *
         * @param t the function argument
         * @return the function result
         */
        R apply(T t);
    }

我們用它生成一個 學生選課的集合。我們輸入的是一個Student類型的集合,返回的是一個 String類型的集合

    @Test
    public void mapTest(){

        studentList.stream()
                .map(student -> student.getSubject())
                .forEach(student -> System.out.println(JSON.toJSONString(student, true)));
    }

結果如下

"體育"
"語文"
"數學"
"體育"
"語文"
"英語"
"體育"

faltMap

將一個類型的集合轉換成另一個類型的流(注意和map區分)

/**
     * Returns a stream consisting of the results of replacing each element of
     * this stream with the contents of a mapped stream produced by applying
     * the provided mapping function to each element.  Each mapped stream is
     * {@link java.util.stream.BaseStream#close() closed} after its contents
     * have been placed into this stream.  (If a mapped stream is {@code null}
     * an empty stream is used, instead.)
     *
     * @return the new stream
     */
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);


		// 他也是 Function接口,但是另一個參數是繼承自 Stream類的
    /**
     * 將一個類型的集合抓換成流.我們把一個字符串流轉換成一個字符流
     */
    @Test
    public void flatMapTest() {

        studentList.stream()
                .flatMap(student -> Arrays.stream(student.getName().split("")))
                .forEach(stu -> System.out.println(JSON.toJSONString(stu,true)));
    }

peek

peek和foreach很相似,區別是 ,一個是中間操作,一個是終端操作。peek用完之後還能被其他操作進行處理。

/**
     * Returns a stream consisting of the elements of this stream, additionally
     * performing the provided action on each element as elements are consumed
     * from the resulting stream.
     *
    
     * @return the new stream
     */
    Stream<T> peek(Consumer<? super T> action);


// 我們看到他的函數接口是Consumer,他是輸入一個參數,但是不會有返回值
@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}
    /**
     * peek 方法
     */
    @Test
    public void peekTest() {
        studentList.stream()
                .peek(student -> System.out.println(student.getName()))
                .forEach(stu -> System.out.println(JSON.toJSONString(stu,true)));
    }
張三丰
{
	"age":20,
	"height":180,
	"name":"張三丰",
	"sex":"男",
	"subject":"體育",
	"teacher":"太上老君",
	"weight":75
}
張無忌
{
	"age":18,
	"height":178,
	"name":"張無忌",
	"sex":"男",
	"subject":"語文",
	"teacher":"文曲星",
	"weight":73
}
趙敏
{
	"age":17,
	"height":170,
	"name":"趙敏",
	"sex":"女",
	"subject":"數學",
	"teacher":"太白金星",
	"weight":50
}
金毛獅王
{
	"age":25,
	"height":176,
	"name":"金毛獅王",
	"sex":"男",
	"subject":"體育",
	"teacher":"太白金星",
	"weight":80
}
周芷若
{
	"age":16,
	"height":168,
	"name":"周芷若",
	"sex":"女",
	"subject":"語文",
	"teacher":"太上老君",
	"weight":48
}
張三
{
	"age":21,
	"height":172,
	"name":"張三",
	"sex":"男",
	"subject":"英語",
	"teacher":"如來",
	"weight":65
}
趙勇
{
	"age":26,
	"height":188,
	"name":"趙勇",
	"sex":"男",
	"subject":"體育",
	"teacher":"太上老君",
	"weight":80
}

我們看到 人名和student類是交替打印的**,也就是執行了一次 peek,執行了一次 foreach**,那麼每次都是這樣嗎?不是的,我們來看下一節

有狀態操作

sorted

sorted是用來排序的

 /**
     * Returns a stream consisting of the elements of this stream, sorted
     * according to the provided {@code Comparator}
     * @return the new stream
     */
    Stream<T> sorted(Comparator<? super T> comparator);

		// 他提供了一個 Comparator接口
		@FunctionalInterface
		public interface Comparator<T> {
    /**
     * Compares its two arguments for order.  Returns a negative integer,
     * zero, or a positive integer as the first argument is less than, equal
     * to, or greater than the second.<p>
     */
    int compare(T o1, T o2);
/**
     * sorted 方法,按照年齡大小排序
     */
    @Test
    public void sortedTest() {
        studentList.stream()
                .peek(student -> System.out.println(student.getName()))
                .sorted((x,y) -> x.getAge()-y.getAge())
                .forEach(stu -> System.out.println(JSON.toJSONString(stu,true)));
    }
張三丰
張無忌
趙敏
金毛獅王
周芷若
張三
趙勇
{
	"age":16,
	"height":168,
	"name":"周芷若",
	"sex":"女",
	"subject":"語文",
	"teacher":"太上老君",
	"weight":48
}
{
	"age":17,
	"height":170,
	"name":"趙敏",
	"sex":"女",
	"subject":"數學",
	"teacher":"太白金星",
	"weight":50
}
{
	"age":18,
	"height":178,
	"name":"張無忌",
	"sex":"男",
	"subject":"語文",
	"teacher":"文曲星",
	"weight":73
}
{
	"age":20,
	"height":180,
	"name":"張三丰",
	"sex":"男",
	"subject":"體育",
	"teacher":"太上老君",
	"weight":75
}
{
	"age":21,
	"height":172,
	"name":"張三",
	"sex":"男",
	"subject":"英語",
	"teacher":"如來",
	"weight":65
}
{
	"age":25,
	"height":176,
	"name":"金毛獅王",
	"sex":"男",
	"subject":"體育",
	"teacher":"太白金星",
	"weight":80
}
{
	"age":26,
	"height":188,
	"name":"趙勇",
	"sex":"男",
	"subject":"體育",
	"teacher":"太上老君",
	"weight":80
}

我們看到,這次是先打印peek的內容,再打印 foreach的內容,爲什麼會這樣呢?

因爲之前我們除只有無狀態的方法,他不需要遍歷完其他全部的操作,所以他就交替打印了,也符合流的特性。但是我們這一次有了有狀態的操作,就只能等到處理完全部元素之後能進行foreach的遍歷操作

distinct

去重

/**
     * Returns a stream consisting of the distinct elements (according to
     * {@link Object#equals(Object)}) of this stream
     *
     * @return the new stream
     */
    Stream<T> distinct();
/**
 * distinct 方法,找出所有老師
 */
@Test
public void distinctTest() {
    studentList.stream()
            .map(student -> student.getTeacher())
            .distinct()
            .forEach(stu -> System.out.println(JSON.toJSONString(stu,true)));
}
"太上老君"
"文曲星"
"太白金星"
"如來"

limit

    /**
     * Returns a stream consisting of the elements of this stream, truncated
     * to be no longer than {@code maxSize} in length.
     *
     * @param maxSize the number of elements the stream should be limited to
     * @return the new stream
     * @throws IllegalArgumentException if {@code maxSize} is negative
     */
    Stream<T> limit(long maxSize);   


   /**
     * limit 方法,只顯示前4個
     */
    @Test
    public void limitTest() {
        studentList.stream()
                .limit(4)
                .forEach(stu -> System.out.println(JSON.toJSONString(stu,true)));
    }
{
	"age":20,
	"height":180,
	"name":"張三丰",
	"sex":"男",
	"subject":"體育",
	"teacher":"太上老君",
	"weight":75
}
{
	"age":18,
	"height":178,
	"name":"張無忌",
	"sex":"男",
	"subject":"語文",
	"teacher":"文曲星",
	"weight":73
}
{
	"age":17,
	"height":170,
	"name":"趙敏",
	"sex":"女",
	"subject":"數學",
	"teacher":"太白金星",
	"weight":50
}
{
	"age":25,
	"height":176,
	"name":"金毛獅王",
	"sex":"男",
	"subject":"體育",
	"teacher":"太白金星",
	"weight":80
}

skip

/**
     * Returns a stream consisting of the remaining elements of this stream
     * after discarding the first {@code n} elements of the stream.
     * If this stream contains fewer than {@code n} elements then an
     * empty stream will be returned.
     
     * @throws IllegalArgumentException if {@code n} is negative
     */
    Stream<T> skip(long n);
    


/**
     * skip 方法,跳過前4個
     */
    @Test
    public void skipTest() {
        studentList.stream()
                .skip(4)
                .forEach(stu -> System.out.println(JSON.toJSONString(stu,true)));
    }
{
	"age":16,
	"height":168,
	"name":"周芷若",
	"sex":"女",
	"subject":"語文",
	"teacher":"太上老君",
	"weight":48
}
{
	"age":21,
	"height":172,
	"name":"張三",
	"sex":"男",
	"subject":"英語",
	"teacher":"如來",
	"weight":65
}
{
	"age":26,
	"height":188,
	"name":"趙勇",
	"sex":"男",
	"subject":"體育",
	"teacher":"太上老君",
	"weight":80
}

skip+limit可以完成分頁功能

    /**
     * limit+skip 方法,完成分頁功能
     */
    @Test
    public void skipAndLimitTest() {
        studentList.stream()
                .skip(1 * 4)
                .limit(4)
                .forEach(stu -> System.out.println(JSON.toJSONString(stu, true)));
    }

終端操作

短路

anyMatch

    /**
     * Returns whether any elements of this stream match the provided
     * predicate.  May not evaluate the predicate on all elements if not
     * necessary for determining the result.  If the stream is empty then
     * {@code false} is returned and the predicate is not evaluated.
     
     * @return {@code true} if any elements of the stream match the provided
     * predicate, otherwise {@code false}
     */
    boolean anyMatch(Predicate<? super T> predicate);



/**
     * anyMatch方法,判斷是否有一個滿足條件
     */
    @Test
    public void anyMatchTest() {
        final boolean b = studentList.stream()
                .peek(student -> System.out.println(student))
                .allMatch(student -> student.getAge() > 100);
        System.out.println(b);
    }

爲啥說是短路操作。我們測試一下就知道

我們看到,它只打印了一個元素。因爲第一個就不滿足,他就不會再往下執行了,直接返回false

Student(name=張三丰, age=20, sex=, subject=體育, height=180, weight=75, teacher=太上老君)
false

allMatch

 /**
     * Returns whether all elements of this stream match the provided predicate.
     * May not evaluate the predicate on all elements if not necessary for
     * determining the result.  If the stream is empty then {@code true} is
     * returned and the predicate is not evaluated
     * @return {@code true} if either all elements of the stream match the
     * provided predicate or the stream is empty, otherwise {@code false}
     */

		// 我們看到他的接口是一個 Predicate ,這個我們之前介紹過,返回的是一個bool值
    boolean allMatch(Predicate<? super T> predicate);




    /**
     * allMatch方法,判斷是否全部滿足輸入的條件
     */
    @Test
    public void allMatchTest() {
        final boolean b = studentList.stream()
                .allMatch(student -> student.getAge() < 100);
        System.out.println(b);
    }

findFirst

/**
     * Returns an {@link Optional} describing the first element of this stream,
     * or an empty {@code Optional} if the stream is empty.  If the stream has
     * no encounter order, then any element may be returned.
     
     * @return an {@code Optional} describing the first element of this stream,
     * or an empty {@code Optional} if the stream is empty
     * @throws NullPointerException if the element selected is null
     */

		// 他返回的是一個 Optional對象
    Optional<T> findFirst();   


/**
     * findFirst方法
     */
    @Test
    public void findFirstTest() {
        final Optional<Student> first = studentList.stream()
                .peek(student -> System.out.println(student))
                .findFirst();
        System.out.println(first.get());
    }

findAny

/**
     * Returns an {@link Optional} describing some element of the stream, or an
     * empty {@code Optional} if the stream is empty.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">short-circuiting
     * terminal operation</a>.
     *
     * <p>The behavior of this operation is explicitly nondeterministic; it is
     * free to select any element in the stream.  This is to allow for maximal
     * performance in parallel operations; the cost is that multiple invocations
     * on the same source may not return the same result.  (If a stable result
     * is desired, use {@link #findFirst()} instead.)
     *
     * @return an {@code Optional} describing some element of this stream, or an
     * empty {@code Optional} if the stream is empty
     * @throws NullPointerException if the element selected is null
     * @see #findFirst()
     */

		// 注意 他的解釋中說了,findAny可以發揮並行操作的性能,但是如果你在並行的時候想要一個穩定的結果,要用 findFirst。
    Optional<T> findAny();
    

    /**
    	* 因爲我們使用的是串行的操作,所以並不影響結果,和findFirst 的結果一樣
    	*/
		@Test
    public void findAnyTest() {
        final Optional<Student> first = studentList.stream()
                .peek(student -> System.out.println(student))
                .findAny();
        System.out.println(first.get());
    }

max

/**
     * Returns the maximum element of this stream according to the provided
     * {@code Comparator}.  This is a special case of a
     * <a href="package-summary.html#Reduction">reduction</a>.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">terminal
     * operation</a>.
     *
     * @param comparator a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                   <a href="package-summary.html#Statelessness">stateless</a>
     *                   {@code Comparator} to compare elements of this stream
     * @return an {@code Optional} describing the maximum element of this stream,
     * or an empty {@code Optional} if the stream is empty
     * @throws NullPointerException if the maximum element is null
     */
		// 返回的是一個 Optional類
    Optional<T> max(Comparator<? super T> comparator);


  /**
     * max 方法測試,輸出最大的年齡。如果從這裏點進入就是使用的 intStream接口,和之前的還不一樣
     */
    @Test
    public void maxTest() {
        final OptionalInt max = studentList.stream()
                .mapToInt(stu -> stu.getAge())
                .max();
        System.out.println(max.getAsInt());
    }


終端非短路

collect

    

		@Test
    public void collectTest(){
        final List<Student> list = studentList.stream()
                .filter(student -> "女".equals(student.getSex()))
                .collect(Collectors.toList());

        System.out.println(JSON.toJSONString(list,true));
    }
[
	{
		"age":17,
		"height":170,
		"name":"趙敏",
		"sex":"女",
		"subject":"數學",
		"teacher":"太白金星",
		"weight":50
	},
	{
		"age":16,
		"height":168,
		"name":"周芷若",
		"sex":"女",
		"subject":"語文",
		"teacher":"太上老君",
		"weight":48
	}
]

groupBy 進行分類

/**
     * Returns a {@code Collector} implementing a "group by" operation on
     * input elements of type {@code T}, grouping elements according to a
     * classification function, and returning the results in a {@code Map}.
     *
     * <p>The classification function maps elements to some key type {@code K}.
     * The collector produces a {@code Map<K, List<T>>} whose keys are the
     * values resulting from applying the classification function to the input
     * elements, and whose corresponding values are {@code List}s containing the
     * input elements which map to the associated key under the classification
     * function.
     */

		// 可以看到,他的參數也是一個 Function接口,
    public static <T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier) {
        return groupingBy(classifier, toList());
    }



    @Test
    public void collectTest(){
//        final List<Student> list = studentList.stream()
//                .filter(student -> "女".equals(student.getSex()))
//                .collect(Collectors.toList());

      // 按照性別分類
        final Map<String, List<Student>> list = studentList.stream()
                .collect(Collectors.groupingBy(s -> s.getSex()));

        System.out.println(JSON.toJSONString(list,true));
    }


{
	"女":[
		{
			"age":17,
			"height":170,
			"name":"趙敏",
			"sex":"女",
			"subject":"數學",
			"teacher":"太白金星",
			"weight":50
		},
		{
			"age":16,
			"height":168,
			"name":"周芷若",
			"sex":"女",
			"subject":"語文",
			"teacher":"太上老君",
			"weight":48
		}
	],
	"男":[
		{
			"age":20,
			"height":180,
			"name":"張三丰",
			"sex":"男",
			"subject":"體育",
			"teacher":"太上老君",
			"weight":75
		},
		{
			"age":18,
			"height":178,
			"name":"張無忌",
			"sex":"男",
			"subject":"語文",
			"teacher":"文曲星",
			"weight":73
		},
		{
			"age":25,
			"height":176,
			"name":"金毛獅王",
			"sex":"男",
			"subject":"體育",
			"teacher":"太白金星",
			"weight":80
		},
		{
			"age":21,
			"height":172,
			"name":"張三",
			"sex":"男",
			"subject":"英語",
			"teacher":"如來",
			"weight":65
		},
		{
			"age":26,
			"height":188,
			"name":"趙勇",
			"sex":"男",
			"subject":"體育",
			"teacher":"太上老君",
			"weight":80
		}
	]
}

Process finished with exit code 0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章