寫在前面
在上一篇《【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新特性時少走彎路。