【Java8新特性】Stream API有哪些中間操作?看完你也可以吊打面試官!!

冰河團隊發表於2020-05-27

寫在前面

在上一篇《【Java8新特性】面試官問我:Java8中建立Stream流有哪幾種方式?》中,一名讀者去面試被面試官暴虐!歸根結底,那哥兒們還是對Java8的新特性不是很瞭解呀!那麼,我們繼續講述Java8的新特性,旨在最終可以讓每位讀者在跳槽面試的過程中吊打面試官!!

Stream的中間操作

多箇中間操作可以連線起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理!而在終止操作時一次性全部處理,稱為“惰性求值” 。 Stream的中間操作是不會有任何結果資料輸出的。

Stream的中間操作在整體上可以分為:篩選與切片、對映、排序。接下來,我們就分別對這些中間操作進行簡要的說明。

篩選與切片

這裡,我將與篩選和切片有關的操作整理成如下表格。

方法 描述
filter(Predicate p) 接收Lambda表示式,從流中排除某些元素
distinct() 篩選,通過流所生成元素的 hashCode() 和 equals() 去 除重複元素
limit(long maxSize) 截斷流,使其元素不超過給定數量
skip(long n) 跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素 不足 n 個,則返回一個空流。與 limit(n) 互補

接下來,我們列舉幾個簡單的示例,以便加深理解。

為了更好的測試程式,我先構造了一個物件陣列,如下所示。

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

其中,Employee類的定義如下所示。

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
    private static final long serialVersionUID = -9079722457749166858L;
    private String name;
    private Integer age;
    private Double salary;
}

Employee類的定義比較簡單,這裡,我就不贅述了。之後的示例中,我們都是使用的Employee物件的集合進行操作。好了,我們開始具體的操作案例。

1.filter()方法

filter()方法主要是用於接收Lambda表示式,從流中排除某些元素,其在Stream介面中的原始碼如下所示。

Stream<T> filter(Predicate<? super T> predicate);

可以看到,在filter()方法中,需要傳遞Predicate介面的物件,Predicate介面又是個什麼鬼呢?點進去看下原始碼。

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

可以看到,Predicate是一個函式式介面,其中介面中定義的主要方法為test()方法,test()方法會接收一個泛型物件t,返回一個boolean型別的資料。

看到這裡,相信大家明白了:filter()方法是根據Predicate介面的test()方法的返回結果來過濾資料的,如果test()方法的返回結果為true,符合規則;如果test()方法的返回結果為false,則不符合規則。

這裡,我們可以使用下面的示例來簡單的說明filter()方法的使用方式。

//內部迭代:在此過程中沒有進行過迭代,由Stream api進行迭代
//中間操作:不會執行任何操作
Stream<Person> stream = list.stream().filter((e) -> {
    System.out.println("Stream API 中間操作");
    return e.getAge() > 30;
});

我們,在執行終止語句之後,一邊迭代,一邊列印,而我們並沒有去迭代上面集合,其實這是內部迭代,由Stream API 完成。

下面我們來看看外部迭代,也就是我們人為得迭代。

//外部迭代
Iterator<Person> it = list.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

2.limit()方法

主要作用為:截斷流,使其元素不超過給定數量。

先來看limit方法的定義,如下所示。

Stream<T> limit(long maxSize);

limit()方法在Stream介面中的定義比較簡單,只需要傳入一個long型別的數字即可。

我們可以按照如下所示的程式碼來使用limit()方法。

//過濾之後取2個值
list.stream().filter((e) -> e.getAge() >30 ).limit(2).forEach(System.out :: println);

在這裡,我們可以配合其他得中間操作,並截斷流,使我們可以取得相應個數得元素。而且在上面計算中,只要發現有2條符合條件得元素,則不會繼續往下迭代資料,可以提高效率。

3.skip()方法

跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素 不足 n 個,則返回一個空流。與 limit(n) 互補。

原始碼定義如下所示。

Stream<T> skip(long n);

原始碼定義比較簡單,同樣只需要傳入一個long型別的數字即可。其含義是跳過n個元素。

簡單示例如下所示。

//跳過前2個值
list.stream().skip(2).forEach(System.out :: println);

4.distinct()方法

篩選,通過流所生成元素的 hashCode() 和 equals() 去 除重複元素。

原始碼定義如下所示。

Stream<T> distinct();

旨在對流中的元素進行去重。

我們可以如下面的方式來使用disinct()方法。

list.stream().distinct().forEach(System.out :: println);

這裡有一個需要注意的地方:distinct 需要實體中重寫hashCode()和 equals()方法才可以使用。

對映

關於對映相關的方法如下表所示。

方法 描述
map(Function f) 接收一個函式作為引數,該函式會被應用到每個元 素上,並將其對映成一個新的元素。
mapToDouble(ToDoubleFunction f) 接收一個函式作為引數,該函式會被應用到每個元 素上,產生一個新的 DoubleStream。
mapToInt(ToIntFunction f) 接收一個函式作為引數,該函式會被應用到每個元 素上,產生一個新的 IntStream。
mapToLong(ToLongFunction f) 接收一個函式作為引數,該函式會被應用到每個元 素上,產生一個新的 LongStream
flatMap(Function f) 接收一個函式作為引數,將流中的每個值都換成另 一個流,然後把所有流連線成一個流

1.map()方法

接收一個函式作為引數,該函式會被應用到每個元 素上,並將其對映成一個新的元素。

先來看Java8中Stream介面對於map()方法的宣告,如下所示。

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

我們可以按照如下方式使用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());

2.flatMap()

接收一個函式作為引數,將流中的每個值都換成另 一個流,然後把所有流連線成一個流。

先來看Java8中Stream介面對於flatMap()方法的宣告,如下所示。

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

我們可以使用如下方式使用flatMap()方法,為了便於大家理解,這裡,我就貼出了測試flatMap()方法的所有程式碼。

/**
     * 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方法就相當於Collaction的add方法,如果add的是個集合得話就會變成二維陣列,而flatMap 的話就相當於Collaction的addAll方法,引數如果是集合得話,只是將2個集合合併,而不是變成二維陣列。

排序

關於排序相關的方法如下表所示。

方法 描述
sorted() 產生一個新流,其中按自然順序排序
sorted(Comparator comp) 產生一個新流,其中按比較器順序排序

從上述表格可以看出:sorted有兩種方法,一種是不傳任何引數,叫自然排序,還有一種需要傳Comparator 介面引數,叫做定製排序。

先來看Java8中Stream介面對於sorted()方法的宣告,如下所示。

Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);

sorted()方法的定義比較簡單,我就不再贅述了。

我們也可以按照如下方式來使用Stream的sorted()方法。

// 自然排序
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());

寫在最後

如果覺得文章對你有點幫助,請微信搜尋並關注「 冰河技術 」微信公眾號,跟冰河學習Java8新特性。

最後,附上Java8新特性核心知識圖,祝大家在學習Java8新特性時少走彎路。

img

相關文章