在學習Java8新特性中Stream API和Optional類的時候,發現學習的時候不繫統,用的時候總是忘記怎麼去使用。所以總結一些實例,便於理解其使用。
Stream API
Stream簡介
Stream 是Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查找、過濾和映射數據等操作。使用Stream API 對集合數據進行操作,就類似於使用SQL 執行的數據庫查詢。也可以使用Stream API 來並行執行操作。簡而言之,Stream API 提供了一種高效且易於使用的處理數據的方式。
什麼是Stream?
Stream是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列。“集合講的是數據,流講的是計算!”
注意事項
- Stream 自己不會存儲元素。
- Stream 不會改變源對象。相反,他們會返回一個持有結果的新Stream。
- Stream 操作是延遲執行的。這意味着他們會等到需要結果的時候才執行。
Stream的使用
Stream操作的三個步驟
- 創建Stream
一個數據源(如:集合、數組),獲取一個流 - 中間操作
一箇中間操作鏈,對數據源的數據進行處理 - 終止操作(終端操作)
一個終止操作,執行中間操作鏈,併產生結果
圖解Stream的操作步驟
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Qb9v9dBL-1592729583891)(F:\知識庫\Java\Java8新特性\Java8新特性.assets\image-20200620202143071.png)]
創建Stream
1.通過集合創建Stream
Java8 中的Collection 接口被擴展,提供了兩個獲取流的方法:
default Stream<E> stream()
: 返回一個順序流default Stream<E> parallelStream()
: 返回一個並行流
2.通過數組創建Stream
Java8 中的Arrays 的靜態方法stream() 可以獲取數組流:
static <T> Stream<T> stream(T[] array)
: 返回一個流
重載形式,能夠處理對應基本類型的數組:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
3.通過Stream的靜態方法of()創建Stream
可以調用Stream類靜態方法of(), 通過顯示值創建一個流。它可以接收任意數量的參數。
public static<T> Stream<T> of(T... values)
: 返回一個流
4.創建無限流
可以使用靜態方法Stream.iterate() 和Stream.generate(),創建無限流。
- 迭代
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
- 生成
public static<T> Stream<T> generate(Supplier<T> s)
示例
@Test
public void test1 () {
//1. Collection 提供了兩個方法 stream() 與 parallelStream()
List<String> list = new ArrayList<>();
//獲取一個順序流
Stream<String> stream = list.stream();
//獲取一個並行流
Stream<String> parallelStream = list.parallelStream();
//2. 通過 Arrays 中的 stream() 獲取一個數組流
Integer[] nums = new Integer[10];
Stream<Integer> stream1 = Arrays.stream(nums);
//3. 通過 Stream 類中靜態方法 of()
Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);
//4. 創建無限流
//迭代
Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
stream3.forEach(System.out::println);
//生成
Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
stream4.forEach(System.out::println);
}
Stream的中間操作
多箇中間操作可以連接起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理!
而在終止操作時一次性全部處理,稱爲“惰性求值”。
新建一個學生類,方便測試。
public class Student {
private String name;
private int age;
private Gender gender;
public Student () {
}
public Student ( String name, int age, Gender gender ) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName () {
return name;
}
public void setName ( String name ) {
this.name = name;
}
public int getAge () {
return age;
}
public void setAge ( int age ) {
this.age = age;
}
public Gender getGender () {
return gender;
}
public void setGender ( Gender gender ) {
this.gender = gender;
}
@Override
public boolean equals ( Object o ) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name) &&
gender == student.gender;
}
@Override
public int hashCode () {
return Objects.hash(name, age, gender);
}
@Override
public String toString () {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
/** 性別枚舉 */
public enum Gender{
/** 男 */
MAN,
/** 女 */
WOMAN;
}
}
創建一個集合方便測試
List<Student> studentList = Arrays.asList(
new Student("李四", 59, Student.Gender.MAN),
new Student("張三", 18, Student.Gender.MAN),
new Student("王五", 28, Student.Gender.MAN),
new Student("趙小苗", 8, Student.Gender.WOMAN),
new Student("趙小苗", 8, Student.Gender.WOMAN),
new Student("李芳", 8, Student.Gender.WOMAN)
);
1.篩選與切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda ,從流中排除某些元素。 |
distinct() | 篩選,通過流所生成元素的hashCode() 和equals() 去除重複元素 |
limit(long maxSize) | 截斷流,使其元素不超過給定數量。 |
skip(long n) | 跳過元素,返回一個扔掉了前n 個元素的流。若流中元素不足n 個,則返回一個空流。與limit(n) 互補 |
使用示例
@Test
public void test2 () {
System.out.println("---測試filter,過濾信息---");
Stream<Student> studentStream = studentList.stream()
.filter(s -> s.getAge() > 20);
studentStream.forEach(System.out::println);
System.out.println("---測試limit,取前1個元素---");
studentList.stream()
.filter((e) -> e.getAge() >= 20)
.limit(1)
.forEach(System.out::println);
System.out.println("---測試skip,跳過1個元素---");
studentList.stream()
.filter((e) -> e.getAge() >= 20)
.skip(1)
.forEach(System.out::println);
System.out.println("---測試distinct,去重---");
studentList.stream()
.distinct()
.forEach(System.out::println);
}
結果:
---測試filter,過濾信息---
Student{name='李四', age=59, gender=MAN}
Student{name='王五', age=28, gender=MAN}
---測試limit,取前1個元素---
Student{name='李四', age=59, gender=MAN}
---測試skip,跳過1個元素---
Student{name='王五', age=28, gender=MAN}
---測試distinct,去重---
Student{name='李四', age=59, gender=MAN}
Student{name='張三', age=18, gender=MAN}
Student{name='王五', age=28, gender=MAN}
Student{name='趙小苗', age=8, gender=WOMAN}
2.映射
方法 | 描述 |
---|---|
map(Function f) | 接收一個函數作爲參數,該函數會被應用到每個元素上,並將其映射成一個新的素。 |
mapToDouble(ToDoubleFunction f) | 接收一個函數作爲參數,該函數會被應用到每個元素上,產生一個新DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一個函數作爲參數,該函數會被應用到每個元素上,產生一個新的IntStream。 |
mapToLong(ToLongFunction f) | 接收一個函數作爲參數,該函數會被應用到每個元素上,產生一個新的LongStream。 |
flatMap(Function f) | 接收一個函數作爲參數,將流中的每個值都換成另一個流,然後把所有流連接成一個流 |
使用示例
public class StreamTest {
@Test
public void test3 () {
Stream<String> stringStream = studentList.stream()
.map(Student::getName);
stringStream.forEach(System.out::println);
//map與flatMap的區別 與list的add、addAll方法區別類似
List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
Stream<String> stream = strList.stream()
.map(String::toUpperCase);
stream.forEach(System.out::println);
Stream<Stream<Character>> stream2 = strList.stream()
.map(StreamTest::filterCharacter);
stream2.forEach((sm) -> {
sm.forEach(System.out::println);
});
System.out.println("---------------------------------------------");
Stream<Character> stream3 = strList.stream()
.flatMap(StreamTest::filterCharacter);
stream3.forEach(System.out::println);
}
private static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (Character ch : str.toCharArray()) {
list.add(ch);
}
return list.stream();
}
}
3.排序
方法 | 描述 |
---|---|
sorted() | 產生一個新流,其中按自然順序排序 |
sorted(Comparatorcomp) | 產生一個新流,其中按比較器順序排序 |
使用示例
@Test
public void test4 () {
System.out.println("---自然排序---");
studentList.stream()
.map(Student::getName)
.sorted()
.forEach(System.out::println);
System.out.println("---定製排序---");
studentList.stream()
.sorted((x,y)->{
if(x.getAge() == y.getAge()){
return x.getName().compareTo(y.getName());
}else{
return Integer.compare(x.getAge(), y.getAge());
}
})
.forEach(System.out::println);
}
結果:
---自然排序---
張三
李四
李芳
王五
趙小苗
趙小苗
---定製排序---
Student{name='李芳', age=8, gender=WOMAN}
Student{name='趙小苗', age=8, gender=WOMAN}
Student{name='趙小苗', age=8, gender=WOMAN}
Student{name='張三', age=18, gender=MAN}
Student{name='王五', age=28, gender=MAN}
Student{name='李四', age=59, gender=MAN}
終止操作
流進行了終止操作後,不能再次使用
1.查找與匹配
方法 | 描述 |
---|---|
allMatch(Predicate p) | 檢查是否匹配所有元素 |
anyMatch(Predicate p) | 檢查是否至少匹配一個元素 |
noneMatch(Predicatep) | 檢查是否沒有匹配所有元素 |
findFirst() | 返回第一個元素 |
findAny() | 返回當前流中的任意元素 |
count() | 返回流中元素總數 |
max(Comparatorc) | 返回流中最大值 |
min(Comparatorc) | 返回流中最小值 |
forEach(Consumerc) | 內部迭代 |
注:forEach(Consumerc),內部迭代(使用Collection 接口需要用戶去做迭代,稱爲外部迭代。相反,Stream API 使用內部迭代——它幫你把迭代做了)。
示例
@Test
public void test5 () {
boolean b1 = studentList.stream()
.allMatch(e -> e.getGender().equals(Student.Gender.WOMAN));
System.out.println("是否有女學生:" + b1);
Optional<Student> first = studentList.stream()
.sorted(( x, y ) -> {
return x.getAge() - y.getAge();
})
.findFirst();
System.out.println("得到年齡最小的學生:\n" + first.get());
long count = studentList.stream()
.filter(e->e.getGender().equals(Student.Gender.WOMAN))
.count();
System.out.println("女生數量:" + count);
}
結果:
是否有女學生:false
得到年齡最小的學生:
Student{name='趙小苗', age=8, gender=WOMAN}
女生數量:3
2.規約
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b) | 可以將流中元素反覆結合起來,得到一個值。返回T |
reduce(BinaryOperator b) | 可以將流中元素反覆結合起來,得到一個值。返回Optional |
注:map 和reduce 的連接通常稱爲map-reduce 模式,因Google 用它來進行網絡搜索而出名。
@Test
public void test6 () {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream()
.reduce(0, ( x, y ) -> x + y);
System.out.println(sum);
System.out.println("----------------------------------------");
Optional<Integer> op = studentList.stream()
.map(Student::getAge)
.reduce(Integer::sum);
System.out.println("所有學生年齡之和" + op.get());
}
結果:
55
----------------------------------------
所有學生年齡之和129
3.收集
方法 | 描述 |
---|---|
collect(Collector c) | 將流轉換爲其他形式。接收一個Collector接口的實現,用於給Stream中元素做彙總的方法 |
Collector 接口中方法的實現決定了如何對流執行收集操作(如收集到List、Set、Map)。但是Collectors 類提供了很多靜態方法,可以方便地創建常見收集器實例,具體方法與實例如下表:
序號 | 方法 | 返回類型 | 描述 |
---|---|---|---|
1 | toList | List | 把流中元素收集到List |
2 | toSet | Set | 把流中元素收集到Set |
3 | toCollection | Collection | 把流中元素收集到創建的集合 |
4 | counting | Long | 計算流中元素的個數 |
5 | summingInt | Integer | 對流中元素的整數屬性求和 |
6 | averagingInt | Double | 計算流中元素Integer屬性的平均值 |
7 | summarizingInt | IntSummaryStatistics | 收集流中Integer屬性的統計值。如:平均值 |
8 | joining | String | 連接流中每個字符串 |
9 | maxBy | Optional | 根據比較器選擇最大值 |
10 | minBy | Optional | 根據比較器選擇最小值 |
11 | reducing | 歸約產生的類型 | 從一個作爲累加器的初始值開始,利用BinaryOperator與 流中元素逐個結合,從而歸約成單個值 |
12 | collectingAndThen | 轉換函數返回的類型 | 包裹另一個收集器,對其結果轉換函數 |
13 | groupingBy | Map<K,List> | 根據某屬性值對流分組,屬性爲K,結果爲V |
14 | partitioningBy | Map<Boolean,List> | 根據true或false進行分區 |
使用示例:
@Test
public void test7 () {
//測試收集collect
List<String> list = studentList.stream()
.map(Student::getName)
.collect(Collectors.toList());
System.out.println("學生姓名轉集合:" + list);
Set<String> set = studentList.stream()
.map(Student::getName)
.collect(Collectors.toSet());
System.out.println("學生姓名轉Set,去重:" + set);
//求最小的學生年齡
Optional<Integer> minAge = studentList.stream()
.map(Student::getAge)
.collect(Collectors.minBy(Integer::compare));
System.out.println("求最小的學生年齡:" + minAge.get());
System.out.println("---根據性別分組---");
Map<Student.Gender, List<Student>> map = studentList.stream()
.collect(Collectors.groupingBy(Student::getGender));
System.out.println(map);
System.out.println("---根據年齡和性別多級分組---");
Map<Student.Gender, Map<String, List<Student>>> map2=studentList.stream()
.collect(Collectors.groupingBy(Student::getGender,Collectors.groupingBy(e->{
if(e.getAge() >= 60) {
return "老年";
} else if(e.getAge() >= 35) {
return "中年";
} else {
return "成年";
}
})));
System.out.println(map2);
System.out.println("---根據年齡分區---");
Map<Boolean, List<Student>> map3 = studentList.stream()
.collect(Collectors.partitioningBy((e) -> e.getAge() >= 30));
System.out.println(map3);
System.out.println("連接流的字符串");
String str = studentList.stream()
.map(Student::getName)
.collect(Collectors.joining("," , "----", "----"));
System.out.println(str);
System.out.println("規約收集,求所有學生年齡的和");
Optional<Integer> sum = studentList.stream()
.map(Student::getAge)
.collect(Collectors.reducing(Integer::sum));
System.out.println(sum.get());
}
結果:
學生姓名轉集合:[李四, 張三, 王五, 趙小苗, 趙小苗, 李芳]
學生姓名轉Set,去重:[李四, 張三, 趙小苗, 王五, 李芳]
求最小的學生年齡:8
---根據性別分組---
{WOMAN=[Student{name='趙小苗', age=8, gender=WOMAN}, Student{name='趙小苗', age=8, gender=WOMAN}, Student{name='李芳', age=8, gender=WOMAN}], MAN=[Student{name='李四', age=59, gender=MAN}, Student{name='張三', age=18, gender=MAN}, Student{name='王五', age=28, gender=MAN}]}
---根據年齡和性別多級分組---
{WOMAN={成年=[Student{name='趙小苗', age=8, gender=WOMAN}, Student{name='趙小苗', age=8, gender=WOMAN}, Student{name='李芳', age=8, gender=WOMAN}]}, MAN={成年=[Student{name='張三', age=18, gender=MAN}, Student{name='王五', age=28, gender=MAN}], 中年=[Student{name='李四', age=59, gender=MAN}]}}
---根據年齡分區---
{false=[Student{name='張三', age=18, gender=MAN}, Student{name='王五', age=28, gender=MAN}, Student{name='趙小苗', age=8, gender=WOMAN}, Student{name='趙小苗', age=8, gender=WOMAN}, Student{name='李芳', age=8, gender=WOMAN}], true=[Student{name='李四', age=59, gender=MAN}]}
連接流的字符串
----李四,張三,王五,趙小苗,趙小苗,李芳----
規約收集,求所有學生年齡的和
129
並行流與串行流
並行流就是把一個內容分成多個數據塊,並用不同的線程分別處理每個數據塊的流。
Java 8 中將並行進行了優化,我們可以很容易的對數據進行並行操作。Stream API 可以聲明性地通過parallel()
與sequential()
在並行流與順序流之間進行切換。
瞭解Fork/Join 框架
Fork/Join 框架:就是在必要的情況下,將一個大任務,進行拆分(fork)成若干個小任務(拆到不可再拆時),再將一個個的小任務運算的結果進行join 彙總.
圖解
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CyG1g9O9-1592729583902)(F:\知識庫\Java\Java8新特性\Java8新特性.assets\image-20200620212853966.png)]
Fork/Join 框架與傳統線程池的區別
採用“工作竊取”模式(work-stealing):
當執行新的任務時它可以將其拆分分成更小的任務執行,並將小任務加到線程隊列中,然後再從一個隨機線程的隊列中偷一個並把它放在自己的隊列中。
相對於一般的線程池實現,fork/join框架的優勢體現在對其中包含的任務處理方式上。
在一般的線程池中,如果一個線程正在執行的任務由於某些原因無法繼續運行,那麼該線程會處於等待狀態.
而在fork/join框架實現中,如果某個子問題由於等待另外一個子問題的完成而無法繼續運行。那麼處理該子問題的線程會主動尋找其他尚未運行的子問題來執行。
這種方式減少了線程的等待時間,提高了性能。
Optional類
Optional類的由來?解決空指針問題
到目前爲止,臭名昭著的空指針異常是導致Java應用程序失敗的最常見原因。
以前,爲了解決空指針異常,Google公司著名的Guava項目引入了Optional類,Guava通過使用檢查空值的方式來防止代碼污染,它鼓勵程序員寫更乾淨的代碼。
受到Google Guava的啓發,Optional類已經成爲Java 8類庫的一部分。
Optional類簡介
Optional 類(java.util.Optional) 是一個容器類,它可以保存類型T的值,代表這個值存在。或者僅僅保存null,表示這個值不存在。原來用null 表示一個值不存在,現在Optional 可以更好的表達這個概念。並且可以避免空指針異常。
Optional類的Javadoc描述如下:
這是一個可以爲null的容器對象。如果值存在則isPresent()方法會返回true,調用get()方法會返回該對象。
Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。
Optional類的使用
創建Optional類對象
Optional.of(T t)
: 創建一個Optional 實例,t必須非空Optional.empty()
: 創建一個空的Optional 實例Optional.ofNullable(T t)
:若 t 不爲 null,創建 Optional 實例,否則創建空實例(t可以爲null)
判斷Optional容器中是否包含對象
boolean isPresent()
: 判斷是否包含對象void ifPresent(Consumer<? super T> consumer)
:如果有值,就執行Consumer接口的實現代碼,並且該值會作爲參數傳給它。
獲取Optional容器的對象
T get()
: 如果調用對象包含值,返回該值,否則拋異常T orElse(T other)
:如果有值則將其返回,否則返回指定的other對象。T orElseGet(Supplier<? extends T> other)
:如果有值則將其返回,否則返回由Supplier接口實現提供的對象。T orElseThrow(Supplier<? extends X> exceptionSupplier)
:如果有值則將其返回,否則拋出由Supplier接口實現提供的異常。
其他
map(Function f)
: 如果有值對其處理,並返回處理後的Optional,否則返回 Optional.empty()
flatMap(Function mapper)
:與 map 類似,要求返回值必須是Optional
示例
@Test
public void test1 () {
//測試創建Optional
Optional<Student> op = Optional.of(new Student( "張三", 18, Student.Gender.MAN));
Optional<String> s = op.map(Student::getName);
String name = s.get();
System.out.println(name);
Optional<Student> op1 = Optional.ofNullable(null);
if (op1.isPresent()){
System.out.println(op1.get());
}else{
System.out.println("此對象爲空");
}
System.out.println(op1.orElse(new Student( "默認", 18, Student.Gender.MAN)));
}
結果:
張三
此對象爲空
Student{name='默認', age=18, gender=MAN}