java8中stream最实用总结和调试技巧
文章目錄
- 背景
- stream介紹
- 1. 什么是流?
- 2. 流的構成
- 3.流的操作類型
- stream使用
- 1. 流的構造與轉換
- 2. 流的操作
- 在idea中如何調試stream
- 總結
背景
? ? ? ?java8推出的集合操作流stream極大的方便了我們的開發,但stream支持的功能十分豐富,我們經常使用到的并不多。在開發中也發現有些人對stream并不熟悉,或者只會使用簡單的功能。
? ? ? ?舉例來說 在 Java 7 中,如果要發現 type 為 grocery 的所有交易,然后返回以交易值降序排序好的交易 ID 集合,我們需要這樣寫:
? ? ? ?而在 Java 8 使用 Stream,代碼更加簡潔易讀;而且使用并發模式,程序執行速度更快。
List<Integer> transactionsIds = transactions.parallelStream().filter(t -> t.getType() == Transaction.GROCERY).sorted(comparing(Transaction::getValue).reversed()).map(Transaction::getId).collect(toList());? ? ? ?包括我自己對一些stream的api也不太熟悉,在使用時需要現去查詢相關的api資料。所以本文對在開發中經常使用到的集合類操作stream的api進行總結,并介紹利用idea開發工具調試stream時的小技巧。
stream介紹
1. 什么是流?
? ? ? ?Stream 不是集合元素,它不是數據結構并不保存數據,它是有關算法和計算的,它更像一個高級版本的 Iterator。原始版本的 Iterator,用戶只能顯式地一個一個遍歷元素并對其執行某些操作;高級版本的 Stream,用戶只要給出需要對其包含的元素執行什么操作,比如 “過濾掉長度大于 10 的字符串”、“獲取每個字符串的首字母”等,Stream 會隱式地在內部進行遍歷,做出相應的數據轉換。
? ? ? ?Stream 就如同一個迭代器(Iterator),單向,不可往復,數據只能遍歷一次,遍歷過一次后即用盡了,就好比流水從面前流過,一去不復返。
? ? ? ?而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顧名思義,當使用串行方式去遍歷時,每個 item 讀完后再讀下一個 item。而使用并行去遍歷時,數據會被分成多個段,其中每一個都在不同的線程中處理,然后將結果一起輸出。Stream 的并行操作依賴于 Java7 中引入的 Fork/Join 框架(JSR166y)來拆分任務和加速處理過程。
Stream 的另外一大特點是,數據源本身可以是無限的。
2. 流的構成
? ? ? ? 獲取一個數據源(source)→ 數據轉換→執行操作獲取想要的結果。每次轉換原有 Stream 對象不改變,返回一個新的 Stream 對象(可以有多次轉換),這就允許對其操作可以像鏈條一樣排列,變成一個管道。
有多種方式生成 Stream Source:
- 從 Collection 和數組
Collection.stream()
Collection.parallelStream()
Arrays.stream(T array) or Stream.of() - 從 BufferedReader
java.io.BufferedReader.lines() - 靜態工廠
java.util.stream.IntStream.range()
java.nio.file.Files.walk() - 自己構建
java.util.Spliterator - 其它
Random.ints()
BitSet.stream()
Pattern.splitAsStream(java.lang.CharSequence)
JarFile.stream()
3.流的操作類型
- Intermediate:一個流可以后面跟隨零個或多個 intermediate 操作。其目的主要是打開流,做出某種程度的數據映射/過濾,然后返回一個新的流,交給下一個操作使用。這類操作都是惰性化的(lazy),就是說,僅僅調用到這類方法,并沒有真正開始流的遍歷。
- Terminal:一個流只能有一個 terminal 操作,當這個操作執行后,流就被使用“光”了,無法再被操作。所以這必定是流的最后一個操作。Terminal 操作的執行,才會真正開始流的遍歷,并且會生成一個結果,或者一個 side effect。
? ? ? ?在對于一個 Stream 進行多次轉換操作 (Intermediate 操作),每次都對 Stream 的每個元素進行轉換,而且是執行多次,這樣時間復雜度就是 N(轉換次數)個 for 循環里把所有操作都做掉的總和嗎?其實不是這樣的,轉換操作都是 lazy 的,多個轉換操作只會在 Terminal 操作的時候融合起來,一次循環完成。我們可以這樣簡單的理解,Stream 里有個操作函數的集合,每次轉換操作就是把轉換函數放入這個集合中,在 Terminal 操作的時候循環 Stream 對應的集合,然后對每個元素執行所有的函數。
? ? ? ? 還有一種操作被稱為 short-circuiting。用以指:
? ? ? ? 對于一個 intermediate 操作,如果它接受的是一個無限大(infinite/unbounded)的 Stream,但返回一個有限的新 Stream。
? ? ? ? 對于一個 terminal 操作,如果它接受的是一個無限大的 Stream,但能在有限的時間計算出結果。
當操作一個無限大的 Stream,而又希望在有限時間內完成操作,則在管道內擁有一個 short-circuiting 操作是必要的。
stream使用
1. 流的構造與轉換
最常使用到的就是集合作為流的source。
Stream stream; //由單獨的值構成 Stream<String> strStream = Stream.of("one", "two", "three", "four");//由數組構成 String [] strArray = new String[] {"a", "bb", "c"}; stream = Stream.of(strArray); stream = Arrays.stream(strArray);//由集合構成,最常用了 List<String> list = Arrays.asList(strArray); stream = list.stream();//對于基本數值型,目前有三種對應的包裝類型的Stream:IntStream、LongStream、DoubleStream IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println); IntStream.range(1, 3).forEach(System.out::println); IntStream.rangeClosed(1, 3).forEach(System.out::println);進階:還可以自己構造流
? ? ? ? Stream.generate通過實現 Supplier 接口,你可以自己來控制流的生成。這種情形通常用于隨機數、常量的 Stream,或者需要前后元素間維持著某種狀態信息的 Stream。由于它是無限的,在管道中,必須利用 limit 之類的操作限制 Stream 大小。
//生成100以內的15個隨機整數,用來構造測試隨機數不失為一種簡便的方式Stream.generate(() -> new Random().nextInt(100)).limit(15).forEach(System.out::println);//Another wayIntStream.generate(() -> (int) (System.nanoTime() % 100)).limit(15).forEach(System.out::println);//random其實提供了更方便的ints()方法new Random().ints().limit(15).forEach(System.out::println);? ? ? ? Stream.generate() 還接受自己實現的 Supplier。例如在構造海量測試數據的時候,用某種自動的規則給每一個變量賦值,用來構造測試數據很方便!
@Testpublic void testSupplier(){Stream.generate(new PersonSupplier()).limit(10).forEach(p -> System.out.println(p.getName() + ", " + p.getAge() + ", " + p.getHeight()));}private class PersonSupplier implements Supplier<PersonDto> {private int index = 0;private Random random = new Random();@Overridepublic PersonDto get() {return new PersonDto( "xiao" + index, index++, random.nextInt(190));}}? ? ? ? iterate 跟 reduce 操作很像,接受一個種子值,和一個 UnaryOperator(例如 f)。然后種子值成為 Stream 的第一個元素,f(seed) 為第二個,f(f(seed)) 第三個,以此類推。在 iterate 時候管道必須有 limit 這樣的操作來限制 Stream 大小。
Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.out.print(x + " "));.2. 流的操作
流可以轉換為數組、集合等
Stream<String> stream = Stream.of("one", "two", "three", "four");// 1. 轉換為數組String[] strArray1 = stream.toArray(String[]::new);// 2. 轉換為集合List<String> list1 = stream.collect(Collectors.toList());List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));Set set1 = stream.collect(Collectors.toSet());Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));// 3. 轉為StringString str = stream.collect(Collectors.joining(",")).toString(); @Data @Accessors(chain = true) public class OriginalDto {private String id;private String regionId;private LocalDateTime deviceTime;private Double noiseValue;private Double noiseStatus;} List<OriginalDto> originalDtoList = new ArrayList<>(10);//獲取originalDtoList中所有id的集合List<String> originalDtoIdList = originalDtoList.stream().map(OriginalDto::getId).collect(Collectors.toList());//根據originalDtoList的deviceTime進行分組Map<LocalDateTime, List<OriginalDto>> dateTimeListMap= originalDtoList.stream().collect(Collectors.groupingBy(OriginalDto::getDeviceTime));Map<Long, Long> baseIdMap = reportInfoLessonList.stream().collect(Collectors.toMap(ILessonStudentBaseDto::getBaseId, lesson -> lesson.getILessonBaseDto().getLessonId(), (o,n) -> n));//求每個deviceTime分組對應的noiseValue的和Map<LocalDateTime, Double> doubleMap = originalDtoList.stream().collect(Collectors.groupingBy(OriginalDto::getDeviceTime, Collectors.summingDouble(OriginalDto::getNoiseValue)));//獲取originalDtoList中所有噪音值的平均值,此時mapToDouble轉換為Double流,也可以求和,最大值,最小值,去重,排序,計數等OptionalDouble avgNoiseOptional = originalDtoList.stream().mapToDouble(OriginalDto::getNoiseValue).average();//對originalDtoList中元素按照deviceTime進行排序originalDtoList = originalDtoList.stream().sorted(Comparator.comparing(OriginalDto::getDeviceTime)).collect(Collectors.toList());//從idList到dayDataDto集合List<DayDataDto> dayDataList = originalDtoIdList.stream().map(id -> {DayDataDto dayDataDto = new DayDataDto();dayDataDto.setId(id);dayDataDto.setDate(LocalDate.now());return dayDataDto;}).collect(Collectors.toList());//滿足noiseStatus=0的regionId前3個的集合List<String> regionId = originalDtoList.stream().filter(originalDto -> originalDto.getNoiseStatus() == 0).map(OriginalDto::getRegionId).limit(3).collect(Collectors.toList());`
在idea中如何調試stream
? ? ? ? stream方便了我們的開發,但是在調試時卻十分不友好,幸好在idea2018之后的版本中,我們也可以查看調試stream各元素的變化。
點擊上圖中紅框所示按鈕即可進入stream,查看各元素變化,如下圖查看各元素排序情況:
而且idea中還支持調試時在當前上下文中執行表達式,這個調試時炒雞炒雞方便,如
總結
總結
以上是生活随笔為你收集整理的java8中stream最实用总结和调试技巧的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HTTP协议那些不得不说的事
- 下一篇: volatile关键字——保证并发编程中