JavaSE - 16JDK8新特性2
(5)強大的Stream API
(5.1)StreamAPI說明
- Java8中兩大最為重要的改變,一個是Lambda表達式;另一個就是Stream API。
- StreamAPI(java.util.stream)把真正的函數式編程風格引入到Java中,讓程序員可以寫出高效、干凈、簡潔的代碼。
- StreamAPI是Java8中處理集合的關鍵抽象概念,可以指定希望對集合進行的操作,也可以執行非常復雜的查找、過濾和映射數據等操作。
- 使用StreamAPI對集合數據進行操作,類似於使用SQL執行的數據庫查詢。也可以使用StreamAPI來並行執行操作。
- Collection 和 Stream集合的區別: Collection是一種靜態的內存數據結構,而Stream是有關計算的。
- 前者是主要面向內存, 存儲在內存中,后者主要是面向CPU, 通過CPU實現計算。
(5.2)什么是Stream
- Stream 是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列。 集合講的是數據,Stream講的是計算。
- Stream 自己不會存儲元素。
- Stream 不會改變源對象。相反, 他們會返回一個持有結果的新Stream。
- Stream 操作是延遲執行的。 這意味着他們會等到需要結果的時候才執行。
- Stream 操作步驟:
-
創建Stream: 一個數據源(如:集合、數組),獲取一個流
-
中間操作: 一個中間操作鏈,對數據源的數據進行處理
-
終止操作: 一旦執行終止操作,就執行中間操作鏈,並產生結果通知。之后,不會再被使用。
-
(5.3)Stream 創建
(5.3.1)創建Stream方式一: 通過集合
- Java 8中的Collection 接口被擴展,提供了兩個獲取流的方法:
- default Stream<E> stream(): 返回一個順序流
- default Stream<E> parallelStream(): 返回一個並行流
List<Employee> employees = EmployeeData.getEmployees(); // default Stream<E> stream(): 返回一個順序流 Stream<Employee> stream = employees.stream(); // parallelStream(): 返回一個並行流 Stream<Employee> parallelStream = employees.parallelStream();
(5.3.2)創建Stream方式二: 通過數組
// 調用Arrays類的 static <T> Stream<T> stream(T[] array): 返回一個流 int[] arr = new int[]{1,2,3,45,6}; IntStream stream2 = Arrays.stream(arr); Employee e1 = new Employee(1,"Tom"); Employee e2 = new Employee(2,"Jack"); Employee[] emp = new Employee[]{e1,e2}; Stream<Employee> stream3 = Arrays.stream(emp);
(5.3.3)創建Stream方式三: 通過Stream的of()
Stream<Integer> stream4 = Stream.of(1,2,3,4,5,6);
Stream<Employee> stream5 = Stream.of(e1,e2);
(5.3.4)創建Stream方式四: 創建無限流
// 迭代:public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) Stream.iterate(0, t -> t+2).forEach(System.out::println); // 遍歷前10個偶數 Stream.iterate(0, t -> t+2).limit(10).forEach(System.out::println); // 生成: public static<T> generate(Supplier<T> s) Stream.generate(Math::random).limit(10).forEach(System.out::println);
(5.4)Stream 中間操作
- 多個中間操作可以連接起來形成一個流水線, 除非流水線上觸發終止操作,否則中間操作不會執行任何的處理。
- 而在終止操作時一次性全部處理,稱為"惰性求值"。
(5.4.1)篩選與切片
- filter(Predicate p): 接收Lambda, 從流中排除某些元素
- distinct(): 篩選,通過流所生成元素的 hashCode() 和 equals() 去除重復元素
- limit(long maxSize): 截斷流, 使其元素不超過給定數量
- skip(long n): 跳過元素,返回一個去掉了前n個元素的流。若流中元素不足n個,則返回一個空流。與limit(n)互補。
List<Employee> employees = EmployeeData.getEmployees(); Stream<Employee> stream = employees.stream(); stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println); stream.limit(3).forEach(System.out::println); stream.skip(3).forEach(System.out::println); stream.distinct().forEach(System.out::println);
(5.4.2)映射
- map(Function f): 接收一個函數作為參數,該參數會被應用到每個元素上,並將其映射成一個新的元素。
- mapToDouble(ToDoubleFunction f): 接收一個函數作為參數,該參數會被應用到每個元素上,產生一個新的DoubleStream。
- mapToInt(ToIntFunction f): 接收一個函數作為參數,該參數會被應用到每個元素上,產生一個新的IntStream。
- mapToLong(ToLongFunction f): 接收一個函數作為參數,該參數會被應用到每個元素上,產生一個新的LongStream。
- flatMap(Function f): 接收一個函數作為參數,將流中的每個值都轉換成另一個流,然后把所有的流連接成一個流。
List<String> list = Arrays.asList("aa","vv","cc","dd"); list.stream().map(str -> str.toUpperCase()).forEach(System.out::println); List<Employee> employees = EmployeeData.getEmployees(); Stream<Employee> stream = employees.stream(); Stream<String> nameStream = stream.map(Employee::getName); nameStream.filter(name -> name.length() > 3).forEach(System.out::println); Stream<Stream<Character>> streamStream = list.stream().map(StreamTest::fromStringToStream); streamStream.forEach(s -> { s.forEach(System.out::println); }); Stream<Character> characterStream = list.stream().flatMap(StreamTest::fromStringToStream); characterStream.forEach(System.out::println);
public static Stream<Character> fromStringToStream(String str){ ArrayList<Character> list = new ArrayList<>(); for(Character c: str.toCharArray()){ list.add(c); } return list.stream(); }
(5.4.3)排序
// sorted():自然排序 List<Integer> list = Arrays.asList(12,43,65,34,87,1,-9,19); list.stream().sorted().forEach(System.out::println); // sorted(Comparator com): 定制排序 List<Employee> employees = EmployeeData.getEmployees(); employees.stream().sorted((e1,e2) ->{return Integer.compare(e1.getAge(), e2.getAge());}).forEach(System.out::println); employees.stream().sorted((e1,e2) ->{ int ageValue = Integer.compare(e1.getAge(), e2.getAge()); if(ageValue != 0){ return ageValue; }else{ return Double.compare(e1.getSalary(), e2.getSalary()); } }).forEach(System.out::println);
(5.5)Stream 終止操作
- 終端操作會從流的流水線生成結果。其結果可以是 任何不是流的值,如List、Integer,甚至是 void。
- 流進行了終止操作后,不能再次使用。
(5.5.1)匹配與查找
- allMatch(Predicate p): 檢查是否匹配所有元素
- anyMatch(Predicate p): 檢查是否至少匹配一個元素
- noneMatch(Predicate p): 檢查是否沒有匹配所有元素
- findFirst(): 返回第一個元素
- findAny(): 返回當前流中的任意元素
// 是否所有的員工年齡都大於18 boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18); System.out.println(allMatch); // 是否有任意一個員工的工資大於10000 boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000); System.out.println(anyMatch); // 是否所有員工都不姓"雷", boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷")); System.out.println(noneMatch); // 返回第一個元素 Optional<Employee> firstEmp = employees.stream().findFirst(); System.out.println(firstEmp); // 返回任意元素 Optional<Employee> anyEmp = employees.parallelStream().findAny(); System.out.println(anyEmp);
// 返回最大值 Stream<Double> salaryStream = employees.stream().map(Employee::getSalary); Optional<Double> maxSalary = salaryStream.max(Double::compare); System.out.println(maxSalary.get()); //Optional[9865.38] 9865.38 // 返回最小值 Optional<Employee> minSalaryEmp = employees.stream().min((e1,e2) -> Double.compare(e1.getSalary(),e2.getSalary())); System.out.println(minSalaryEmp); //Optional[Employee{id=1008, name='扎克伯格', age=35, salary=2500.38}] // forEach(Consumer c): 內部迭代 employees.stream().forEach(System.out::println);
(5.5.2)歸約
- reduce(T iden, BinaryOperator b): 可以將流中元素反復結合起來,得到一個值。返回T
- reduce(BinaryOperator b): 可以將流中元素反復結合起來,得到一個值。返回 Optional<T>
- 備注: map 和reduce 的連接通常稱為 map-reduce模式,因Google用它進行網絡搜索而出名。
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10); int sum = list.stream().reduce(0,Integer::sum); System.out.println(sum); // 55 List<Employee> employees = EmployeeData.getEmployees(); Optional<Double> salarySum = employees.stream().map(e -> e.getSalary()).reduce(Double::sum); System.out.println(salarySum); // Optional[48675.03999999999] Optional<Double> salarySum2 = employees.stream().map(Employee::getSalary).reduce((d1,d2) -> d1+d2); System.out.println(salarySum2); // Optional[48675.03999999999]
(5.5.2)收集
- collect(Collector c): 將流轉換為其他形式。接收一個Collector接口的實現,用於給Stream中元素做匯總的方法。
Collector 接口中方法的實現決定了如何對流執行收集的操作(如收集到List、Set、Map)。
另外,Collectors實用類提供了很多靜態方法,可以創建常見收集器實例。
方法 | 返回類型 | 作用 |
toList | List<T> | 把流中元素收集到List |
toSet | Set<T> | 把流中元素收集到Set |
toCollection | Collection<T> | 把流中元素收集到創建的集合 |
counting | Long | 計算流中元素的個數 |
summingInt | Integer | 對流中元素的整數屬性求和 |
avaragingInt | Double | 計算流中元素Integer屬性的平均值 |
summarizingInt | IntSummaryStatistics | 收集流中Integer屬性的統計值。如平均值、最大值、最小值、總數量 |
List<Employee> employees = EmployeeData.getEmployees(); List<Employee> empList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList()); empList.forEach(System.out::println); Set<Employee> empSet= employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet()); empSet.forEach(System.out::println);
Collection<Employee> emps = employees.stream().collect(Collectors.toCollection(ArrayList::new)); System.out.println(emps); long count = employees.stream().collect(Collectors.counting()); System.out.println(count); // 8 double totalSalary = employees.stream().collect(Collectors.summingDouble(Employee::getSalary)); System.out.println(totalSalary); //48675.04 double avgSalary = employees.stream().collect(Collectors.averagingDouble(Employee::getSalary)); System.out.println(avgSalary); //6084.38 DoubleSummaryStatistics dss = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalary)); System.out.println(dss.getMax() + "--" + dss.getMin() + "--" + dss.getAverage()); //9865.38--2500.38--6084.38
(6)Optional類
- 為了解決空指針異常,Google公司的Guava項目引入了Optional類,Guava通過使用檢查空值的方法來防止代碼污染。
- 受Google Guava啟發,Optional類已經成為Java 8類庫的一部分。
- Optional<T> 類(java.util.Optional)是一個容器類,可以保存類型T的值,代表這個值存在。或者僅僅保存null,表示這個值不存在。
- 原來用null表示一個值不存在,現在Optional可以更好的表達這個概念,並且可以避免空指針異常。
- Optional類的JavaDoc描述如下: 這是一個可以為null的容器對象。
- 如果值存在則isPresent()方法會返回true, 調用get()方法會返回該對象。
(6.1)Optional類方法
- 創建Optional類對象的方法:
- Optional.of(T t): 創建一個 Optional實例,t 必須非空
- Optional.empty(): 創建一個空的Optional實例
- Optional.ofNullable(T t): 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接口實現提供的異常。
Girl girl = new Girl(); girl = null; // Optional<Girl> optionalGirl = Optional.of(girl); // java.lang.NullPointerException Girl girl2 = new Girl(); girl2 = null; Optional<Girl> optionalGirl2 = Optional.ofNullable(girl2); Girl girl3 = optionalGirl2.orElse(new Girl("春天",11)); System.out.println(girl3); // Girl{name='null', age=0, boy=null} 未添加 girl2 =null時 // Girl{name='春天', age=11, boy=null}
未做判斷時: 報空指針異常
public String getGirlName(Boy boy){ return boy.getGirl().getName(); } @Test public void test2(){ Boy boy = new Boy(); String girlName = getGirlName(boy); // java.lang.NullPointerException System.out.println(girlName); }
做判斷: 返回null
public String getGirlName(Boy boy){ // return boy.getGirl().getName(); if(boy != null){ Girl girl = boy.getGirl(); if(girl != null){ return girl.getName(); } } return null; }
使用Optional 實現
public String getGirlName2(Boy boy){ Optional<Boy> boyOptional = Optional.ofNullable(boy); Boy boy2 = boyOptional.orElse(new Boy(new Girl("地理",23))); Girl girl = boy2.getGirl(); Optional<Girl> girlOptional = Optional.ofNullable(girl); Girl girl2 = girlOptional.orElse(new Girl("古老",12)); return girl2.getName(); }
// boy=null時 boy2將Girl進行賦值; boy=new Boy()時 boy非空,到了 girl2時對Girl進行賦值 @Test public void test3(){ Boy boy = null; boy = new Boy(); String girlName = getGirlName2(boy); System.out.println(girlName); // boy=null: 地理 boy=new Boy():古老 }
Optional<Object> op1 = Optional.empty(); if(!op1.isPresent()){ System.out.println("數據為空"); // 數據為空 } System.out.println(op1); // Optional.empty System.out.println(op1.isPresent()); // false System.out.println(op1.get()); // java.util.NoSuchElementException: No value present
String str = "hello"; str = null; // of()要求t非空,如t為空,get()方法報錯 Optional<String> op2 = Optional.of(str); System.out.println(op2.get()); // hello 加入str=null: java.lang.NullPointerException
String str2 = "beijn"; str2 = null; Optional<String> op3 = Optional.ofNullable(str2); String str3 = op3.orElse("shanho"); System.out.println(str3); // beijn 加入str2=null: shanho