Java8實戰之Stream
前言
在前面一個小節中,我們已經學習了行為參數化以及Lambda表達式,通過Lambda表達式,可以使得代碼更加簡潔,尤其是當一個方法隻需要使用一次的時候,然而,如果Java8中隻有Lambda表達式的話,那還是不足以讓人感到興奮的,個人感覺,Java8中最有意思,也是最友善的功能,莫過于
Stream
了
Stream初窺
Stream
可以翻譯為流,實際上其操作也是,流操作是Java8中引入的新功能,提供了更加強大的資料疊代處理方式,通過流式寫法,提供了簡潔的文法,主要注意的是
Stream
需要配合Lambda表達式來使用,這更加展現了行為參數化的思想,Java8通過将既定的操作封裝好,同時,将對應的具體行為留給使用者,極大地提高了操作的效率。
Stream
的出現,可以說是用于替代傳統的容器操作的,在傳統的容器操作中,當需要對容器中的某些元素進行操作的時候,我們需要疊代容器,然後篩選出合适的對象,然後再将其存放到另外的容器中,從上面的描述中,可以看到,其中的很大一部分操作:疊代容器,篩選對象,重新存放基本都是固定的,而每次都進行手動操作,顯然是比較繁瑣的,
Stream
則提供了更加便捷的操作,隻需要通過對應的操作模式,然後給出對應的條件,即可實作對既定元素的操作。
為了下面的操作友善,我們先構造需要的元素
// User對象
class User {
private Integer id;
private String name;
private Integer age;
// 省略set,get,toString方法
}
// 構造資料
public static List<User> generateUserData() {
Random random = new Random();
List<User> users = new ArrayList<>();
for (int i = ; i < ; i++) {
users.add(new User(i, "user" + i, random.nextInt()));
}
return users;
}
假設現在有一個場景,我們需要從上面的清單中選取年齡大于20歲的對象,在傳統的容器操作中,一般我們會這樣操作
public List<User> getUserOlderThan20() {
List<User> users = generateUserData();
List<User> result = new ArrayList<>();
for (User user : users) {
if (user.getAge() > ) {
result.add(user);
}
}
return result;
}
而在Java8中,我們可以用更加簡潔的方式來實作上面的操作
public List<User> getUserOlderThan20() {
List<User> users = generateUserData();
List<User> result = users.stream()
.filter(user -> user.getAge() > )
.collect(Collectors.toList());
return result;
}
或者上面的案例看上去并沒有那麼有優勢,那麼我們來看下下面的案例,根據年齡對使用者進行分組,年齡在1-30為年輕人,31-60為中年人,60以上為老年人(例子例子,沒有實際價值)
傳統的操作,我們需要如下操作
public void groupUser() {
List<User> users = generateUserData();
Map<String, List<User>> userGroup = new HashMap<>();
for (User user : users) {
if (user.getAge() > && user.getAge() <= ) {
List<User> young = userGroup.get("young");
if (young == null) {
young = new ArrayList<>();
userGroup.put("young", young);
}
userGroup.get("young").add(user);
}else if (user.getAge() <= ) {
List<User> middle = userGroup.get("middle");
if (middle == null) {
middle = new ArrayList<>();
userGroup.put("middle", middle);
}
userGroup.get("middle").add(user);
}else {
List<User> old = userGroup.get("old");
if (old == null) {
old = new ArrayList<>();
userGroup.put("old", old);
}
userGroup.get("old").add(user);
}
}
System.out.println(userGroup);
}
可以看到,上面的操作還是挺繁瑣的,而且比較容易出錯,而在Java8中,我們則可以采用如下操作
public void testStream() {
List<User> users = generateUserData();
Map<String, List<User>> result = users.stream()
.collect(Collectors.groupingBy(
user -> {
if (user.getAge() > && user.getAge() <= ) {
return "young";
} else if (user.getAge() <= ) {
return "middle";
} else {
return "old";
}}
));
System.out.println(result);
}
可以看到,代碼量以及自描述性的對比還是挺明顯的,
Stream
配合
Lambda
表達式,可以使得之前比較繁瑣的容器操作,變得非常簡單,而且,代碼本身的自解釋性也更強
Stream操作
在前面我們已經見識到了
Stream
本身的特點–流式操作以及友善性,接下來我們來詳細學習
Stream
的用法。
Stream
的操作可以分為兩種,一種是中間操作,例如前面的
filter()
操作,一種是結束操作,例如前面的
collect()
操作,每一個中間操作,都傳回一個
Stream
,經過本次處理之後的
Stream
,結束操作則産生終結,其結果要麼是數字,要麼是字元串,要麼是集合等等,總之就不再是
Stream
,也就是說,一個
Stream
可以有多個中間操作,但隻能有一個結束操作
中間操作
比較常用的幾種中間操作列舉如下,更多的内容參考API即可
-
,過濾操作,入參為filter()
Predicate<? super T> predicate
-
,限制操作,入參為limit()
long maxSize
-
,跳過操作,入參為skip()
long n
-
,去重操作,沒有入參,底層使用的是distinct()
進行去重Set
-
,排序操作,可以傳入自定義的比較器sorted()
Comparator<? super T> comparator
-
,檢查操作,用于調試操作,入參peek()
Consumer<? super T> action
-
,将Stream中的元素映射為其他元素,入參map()
Function<? super T, ? extends R> mapper
-
,将Stream轉為mapToDouble()
,避免裝箱機制所帶來的開銷DoubleStream
-
,将Stream轉為mapToLong()
,避免裝箱機制所帶來的開銷LongStream
-
,将Stream轉為mapToInt()
,避免裝箱機制所帶來的開銷IntStream
-
-
,将多個Stream轉為一個,注意與flatMap()
的差別,入參map()
Function<? super T, ? extends Stream<? extends R>> mapper
結束操作
比較常用的幾個結束操作列舉如下,更多的内容參考API即可
-
,統計元素個數count()
-
,對每個元素執行操作,入參forEach()
Consumer<? super T> action
-
,擷取第一個元素findFirst()
-
,擷取任意一個元素findAny()
-
,檢查元素是否至少有一個比對,入參anyMatch()
Predicate<? super T> predicate
-
,檢查所有元素是否都比對,入參allMatch()
Predicate<? super T> predicate
-
,将所有内容收集起來,入參collect()
,JDK中提供了衆多的Collector<? super T, A, R> collector
的實作,是以,基本上不用自己實作Collector
-
,将内容進行分組,有三個不同的版本groupingBy()
-
,僅能進行一次分組groupingBy(Function<? super T, ? extends K> classifier)
-
,注意第二個參數可以是另一個groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)
,也就是說,可以通過多次的複合,達到多次分組,或者分組後再進行其他的操作Collector
-
,自己提供一個容器,而不是使用預設的容器groupingBy(Function<? super T, ? extends K> classifier,Supplier<M> mapFactory, Collector<? super T, A, D> downstream)
-
-
,等價于前面的counting()
Stream.count()
-
精簡版的partitioningBy()
,僅能支援groupingBy()
、true
兩種分組false
-
,字元串連接配接,需要注意,如果Stream的内容本身不是字元串流,則需要先joining()
操作一下,将其轉為字元串流,可以指定分隔符,字首,字尾map()
-
,将結果合并為ListtoList()
-
,将結果合并為SettoSet()
-
,将結果轉為MaptoMap()
-
,将結果轉為并發MaptoConcurrentMap()
-
-
,根據條件合并結果,可以說,上面的所有結束操作,基本上都可以通過reduce()
來實作,reduce()
有三個不同形式的參數,當JDK所提供的合并操作不滿足需求時,可以通過reduce
來實作自定義的合并操作reduce
-
T reduce(T identity, BinaryOperator<T> accumulator)
-
Optional<T> reduce(BinaryOperator<T> accumulator)
-
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
-
Stream操作執行個體
為了更好地了解上面的内容,我們通過幾個小例子來實際操作一下
// 列印出年齡在30歲以上的所有使用者
users.stream()
.filter(user -> user.getAge() > )
.forEach(System.out::println);
// 如果換成 .count(),則是統計使用者的個數
// 分組并且統計各個分組的人數
Map<String, Long> collect = users.stream()
.collect(groupingBy(user -> {
if (user.getAge() <= ) {
return "young";
} else if (user.getAge() <= ) {
return "middle";
} else {
return "old";
}
}, counting()));
// 分組并且去重
Map<String, Set<User>> collect = users.stream()
.collect(groupingBy(user -> {
if (user.getAge() <= ) {
return "young";
} else if (user.getAge() <= ) {
return "middle";
} else {
return "old";
}
}, toSet()));
關于Stream的介紹,大緻就到這裡了,為了更好地掌握Stream,需要在實際使用中多加練習,多加研究才是
總結
本小節主要學習了Stream的内容,通過對比Stream與傳統的Collection操作,可以看出,通過Stream來操作容器,代碼将變得更加簡潔,而且,其可閱讀行也更強,出錯的機率也會更低,畢竟不用再自己關心疊代的過程,最後,通過幾個簡單的小例子,展示了Stream中兩種不同的操作,中間操作以及結束操作,當然,關于Stream的更多内容,還是需要在實際使用中不斷發現,不斷研究,加油。