<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
說明
本文用範例介紹stream的使用。
JDK8新增了Stream(流操作) 處理集合的資料,可執行查詢、過濾和對映資料等操作。
使用Stream API 對集合資料進行操作,就類似於使用 SQL 執行的資料庫查詢。可以使用 Stream API 來並行執行操作。
簡而言之,Stream API 提供了一種高效且易於使用的處理資料的方式。
特點
不是資料結構,不會儲存資料。
大部分不修改原來的資料來源,它會將操作後的資料儲存到另外一個物件中。
peek方法可以修改流中元素
惰性求值,流在中間處理過程中,只對操作進行記錄,不會立即執行,需等到執行終止操作的時候才會進行實際的計算。
Stream操作步驟
建立Stream=> 轉換Stream(中間操作)=> 產生結果(終止操作)
注意:這只是一般操作。實際程式設計時,建立必須有,而中間操作與終止操作是可選的。
操作分類
無狀態:指元素的處理不受之前元素的影響;
有狀態:指該操作只有拿到所有元素之後才能繼續下去。
非短路操作:指必須處理所有元素才能得到最終結果;
短路操作:指遇到某些符合條件的元素就可以得到最終結果,如 A || B,只要A為true,則無需判斷B的結果。
本文的公共程式碼
class User { private String name; private Integer age; public User(String name, Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + ''' + ", age=" + age + '}'; } }
Collection下的 stream() 和 parallelStream() 方法
List<String> list = new ArrayList<>(); Stream<String> stream = list.stream(); //序列流 Stream<String> parallelStream = list.parallelStream(); //並行流
Arrays 中的 stream() 方法,將陣列轉成流
Integer[] nums = new Integer[10]; Stream<Integer> stream = Arrays.stream(nums);
Stream中的靜態方法:of()、iterate()、generate()
Stream<Integer> stream = Stream.of(1,2,3,4,5,6); stream.forEach(System.out::println); // 輸出:1 2 3 4 5 6 Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6); stream2.forEach(System.out::println); // 輸出:0 2 4 6 8 10 Stream<Double> stream3 = Stream.generate(Math::random).limit(2); stream3.forEach(System.out::println); // 輸出:兩個亂數
BufferedReader.lines() 方法,將每行內容轉成流
BufferedReader reader = new BufferedReader(new FileReader("F:\test_stream.txt")); Stream<String> lineStream = reader.lines(); lineStream.forEach(System.out::println);
Pattern.splitAsStream() 方法,將字串分隔成流
Pattern pattern = Pattern.compile(","); Stream<String> stringStream = pattern.splitAsStream("a,b,c,d"); stringStream.forEach(System.out::println); //輸出:a b c d
方法
方法 | 說明 |
---|---|
filter | 過濾流中的某些元素(只保留返回值為true的項) |
limit(n) | 獲取前n個元素 |
skip(n) | 跳過前n個元素,配合limit(n)可實現分頁 |
distinct | 通過流中元素的 hashCode() 和 equals() 去除重複元素 |
單個元素篩選(過濾)、去重、跳過、獲取前n個
List<Integer> list = new ArrayList<>(Arrays.asList(6, 4, 6, 7, 3, 9, 8, 10, 12, 14, 14)); List<Integer> newList = list.stream() .filter(s -> s > 5) //6 6 7 9 8 10 12 14 14 .distinct() //6 7 9 8 10 12 14 .skip(2) //9 8 10 12 14 .limit(2) //9 8 .collect(Collectors.toList());
根據物件屬性去重
List<User> list = new ArrayList<User>() {{ add(new User("Tony", 20, "12")); add(new User("Pepper", 20, "123")); add(new User("Tony", 22, "1234")); add(new User("Tony", 22, "12345")); }}; //只通過名字去重 List<User> streamByNameList = list.stream().collect(Collectors.collectingAndThen( Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList::new )); System.out.println(streamByNameList); //[User{name='Pepper', age=20, Phone='123'}, // User{name='Tony', age=20, Phone='12'}] //通過名字和年齡去重 List<User> streamByNameAndAgeList = list.stream().collect(Collectors.collectingAndThen( Collectors.toCollection( () -> new TreeSet<>(Comparator.comparing(o -> o.getName() + o.getAge()))), ArrayList::new )); System.out.println(streamByNameAndAgeList); //[User{name='Pepper', age=20, Phone='123'}, // User{name='Tony', age=20, Phone='12'}, // User{name='Tony', age=22, Phone='1234'}]
collectingAndThen 這個方法的意思是: 將收集的結果轉換為另一種型別。
因此上面的方法可以理解為:把 new TreeSet<>(Comparator.comparingLong(BookInfoVo::getRecordId))這個set轉換為 ArrayList。
方法
方法 | 說明 |
---|---|
map | 函數作為引數,該函數被應用到每個元素,並將其對映成一個新的元素。新值型別可以和原來的元素的型別不同。 |
flatMap | 函數作為引數,將流中每個值換成另一個流,再把所有流連成一個流。 新值型別可以和原來的元素的型別不同。 |
mapToInt/Long/Double | 跟map差不多。只是將其轉為基本型別。 |
flatMapToInt/Long/Double | 跟flatMap差不多。只是將其轉為基本型別。 |
新值型別和原來的元素的型別相同範例
List<String> list = Arrays.asList("a,b,c", "1,2,3"); //將每個元素轉成一個新的且不帶逗號的元素 Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", "")); s1.forEach(System.out::println); // abc 123 Stream<String> s2 = list.stream().flatMap(s -> { //將每個元素轉換成一個stream String[] split = s.split(","); Stream<String> s3 = Arrays.stream(split); return s3; }); s2.forEach(System.out::println); // a b c 1 2 3
新值型別和原來的元素的型別不同範例
User u1 = new User("aa", 10); User u2 = new User("bb", 20); User u3 = new User("cc", 10); List<User> list = Arrays.asList(u1, u2, u3); Set<Integer> ageSet = list.stream().map(User::getAge).collect(Collectors.toSet()); ageSet.forEach(System.out::println); //20 10 int[] ageInt = list.stream().map(User::getAge).mapToInt(Integer::intValue).toArray(); //下邊這樣也可以 //Integer[] ages = list.stream.map(User::getAge).toArray(Integer[]::new); for (int i : ageInt) { System.out.println(i); } //10 20 10
map的原型為:<R> Stream<R> map(Function<? super T, ? extends R> mapper);
上邊例子中,將Student::getAge作為引數,其實際為:<R> Stream<Integer> map(Function<? super Student, ? extends Integer> mapper);
方法
方法 | 說明 |
---|---|
sorted() | 自然排序,流中元素需實現Comparable介面。 例:list.stream().sorted() |
sorted(Comparator com) | 客製化排序。常用以下幾種: list.stream().sorted(Comparator.reverseOrder()) list.stream().sorted(Comparator.comparing(Student::getAge)) list.stream().sorted(Comparator.comparing(Student::getAge).reversed()) |
範例
List<String> list = Arrays.asList("aa", "ff", "dd"); //String 類自身已實現Comparable介面 list.stream().sorted().forEach(System.out::println); System.out.println("------------------------------------"); User u1 = new User("dd", 40); User u2 = new User("bb", 20); User u3 = new User("aa", 20); User u4 = new User("aa", 30); List<User> userList = Arrays.asList(u1, u2, u3, u4); //按年齡升序 userList.stream().sorted(Comparator.comparing(User::getAge)) .forEach(System.out::println); System.out.println("------------------------------------"); //先按年齡升序,年齡相同則按姓名升序 userList.stream().sorted( (o1, o2) -> { if (o1.getAge().equals(o2.getAge())) { return o1.getName().compareTo(o2.getName()); } else { return o1.getAge().compareTo(o2.getAge()); } } ).forEach(System.out::println);
結果
aa
dd
ff
------------------------------------
User{name='bb', age=20}
User{name='aa', age=20}
User{name='aa', age=30}
User{name='dd', age=40}
------------------------------------
User{name='aa', age=20}
User{name='bb', age=20}
User{name='aa', age=30}
User{name='dd', age=40}
方法
方法 | 說明 |
---|---|
peek | 類似於map,能得到流中的每一個元素。 但map接收的是一個Function表示式,有返回值; 而peek接收的是Consumer表示式,沒有返回值。 |
範例
User u1 = new User("dd", 40); User u2 = new User("bb", 20); User u3 = new User("aa", 20); User u4 = new User("aa", 30); List<User> list = Arrays.asList(u1, u2, u3, u4); List<User> list1 = list.stream() .peek(o -> o.setAge(100)) .collect(Collectors.toList()); System.out.println(list1);
結果:
[User{name='dd', age=100}, User{name='bb', age=100}, User{name='aa', age=100}, User{name='aa', age=100}]
方法
方法 | 說明 |
---|---|
allMatch | 接收一個 Predicate 函數,當流中每個元素都符合該斷言時才返回true,否則返回false |
noneMatch | 接收一個 Predicate 函數,當流中每個元素都不符合該斷言時才返回true,否則返回false |
anyMatch | 接收一個 Predicate 函數,只要流中有一個元素滿足該斷言則返回true,否則返回false |
findFirst | 返回流中第一個元素 |
findAny | 返回流中的任意元素 |
count | 返回流中元素的總個數 |
max | 返回流中元素最大值 |
min | 返回流中元素最小值 |
範例1:單個型別
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); // 匹配 boolean allMatch = list.stream().allMatch(e -> e > 10); //false boolean noneMatch = list.stream().noneMatch(e -> e > 10); //true boolean anyMatch = list.stream().anyMatch(e -> e > 4); //true // 獲取第一個/第任意個 Integer findFirst = list.stream().findFirst().get(); //1 Integer findAny = list.stream().findAny().get(); //1 // 計數、最大值、最小值 long count = list.stream().count(); //5 Integer max = list.stream().max(Integer::compareTo).get(); //5 Integer min = list.stream().min(Integer::compareTo).get(); //1
範例2:獲取物件中的欄位的最值
User u1 = new User("dd", 40); User u2 = new User("bb", 20); User u3 = new User("aa", 20); User u4 = new User("aa", 30); List<User> list = Arrays.asList(u1, u2, u3, u4); //獲取最小年齡的使用者。 User user1 = list.stream() .min(Comparator.comparing(User::getAge)) .get(); System.out.println(user1); System.out.println("------------------------------------"); //獲取先按姓名升序,姓名相同則按年齡升序。然後獲取最小的那個(第一個) User user = list.stream().min((o1, o2) -> { if (o1.getAge().equals(o2.getAge())) { return o1.getName().compareTo(o2.getName()); } else { return o1.getAge().compareTo(o2.getAge()); } }).get(); System.out.println(user);
結果
User{name='bb', age=20}
------------------------------------
User{name='aa', age=20}
方法
方法 | 說明 |
---|---|
collect | 接收一個Collector範例,將流中元素收整合另外一個資料結構。 |
Collector範例一般由Collectors的靜態方法取得。例如:Collectors.toList()
公共程式碼
User u1 = new User("dd", 40); User u2 = new User("bb", 20); User u3 = new User("aa", 20); User u4 = new User("aa", 30); List<User> list = Arrays.asList(u1, u2, u3, u4);
轉換
字串分隔符連線
String joinName = list.stream().map(User::getName).collect(Collectors.joining(",", "(", ")")); System.out.println(joinName); //(dd,bb,aa,aa)
轉成list
List<Integer> ageList = list.stream().map(User::getAge).collect(Collectors.toList()); System.out.println(ageList); //[40, 20, 20, 30]
轉成set
Set<Integer> ageSet = list.stream().map(User::getAge).collect(Collectors.toSet()); System.out.println(ageSet); //[20, 40, 30]
轉成map(注:key不能相同,否則報錯)
User s1 = new User("dd", 40); User s2 = new User("bb", 20); User s3 = new User("aa", 20); List<User> list = Arrays.asList(s1, s2, s3); Map<String, Integer> ageMap = list.stream().collect(Collectors.toMap(User::getName, User::getAge)); System.out.println(ageMap); //{aa=20, bb=20, dd=40}
本處我將重複的名字給去掉了一個,因為如果key有重複的會報錯。
三個引數的map
第一個引數就是用來生成key值的,第二個引數就是用來生成value值的。
第三個引數用在key值衝突的情況下:若新元素產生的key在Map中已經出現過了,第三個引數就會定義解決的辦法。
User u1 = new User("aa", 10); User u2 = new User("bb", 20); User u3 = new User("cc", 10); User u4 = new User("bb", 30); List<User> list = new ArrayList<>(Arrays.asList(u1, u2, u3, u4)); Map<String, List<User>> listMap = list.stream().collect(Collectors.toMap(User::getName, o -> { List<User> list1 = new ArrayList<>(); list1.add(o); return list1; }, (r1, r2) -> { r1.addAll(r2); return r1; } ) ); System.out.println(listMap);
結果
{aa=[User{name='aa', age=20}, User{name='aa', age=30}], bb=[User{name='bb', age=20}], dd=[User{name='dd', age=40}]}
聚合
聚合(總數、平均值、最大最小值等)
//1.使用者總數 Long count = list.stream().collect(Collectors.counting()); System.out.println(count); //4 //2.最大年齡 (最小的minBy同理) Integer maxAge = list.stream().map(User::getAge).collect(Collectors.maxBy(Integer::compare)).get(); System.out.println(maxAge); //40 //3.所有人的年齡 Integer sumAge = list.stream().collect(Collectors.summingInt(User::getAge)); System.out.println(sumAge); //110 //4.平均年齡 Double averageAge = list.stream().collect(Collectors.averagingDouble(User::getAge)); System.out.println(averageAge); // 27.5 // 統計上邊所有資料 DoubleSummaryStatistics stat = list.stream().collect(Collectors.summarizingDouble(User::getAge)); System.out.println("count:" + stat.getCount() + " max:" + stat.getMax() + " sum:" + stat.getSum() + " average:" + stat.getAverage()); //count:4 max:40.0 sum:110.0 average:27.5
分組
//根據年齡分組 Map<Integer, List<User>> listMap = list.stream().collect(Collectors.groupingBy(User::getAge)); for (Map.Entry<Integer, List<User>> entry : listMap.entrySet()) { System.out.println(entry.getKey() + "-->" + entry.getValue()); } //20-->[User{name='bb', age=20}, User{name='aa', age=20}] //40-->[User{name='dd', age=40}] //30-->[User{name='aa', age=30}]
多重分組
// 先根據年齡分再根據 Map<Integer, Map<String, List<User>>> ageNameMap = list.stream().collect( Collectors.groupingBy(User::getAge, Collectors.groupingBy(User::getName))); for (Map.Entry<Integer, Map<String, List<User>>> entry : ageNameMap.entrySet()) { System.out.println(entry.getKey() + "-->" + entry.getValue()); } //20-->{aa=[User{name='aa', age=20}], bb=[User{name='bb', age=20}]} //40-->{dd=[User{name='dd', age=40}]} //30-->{aa=[User{name='aa', age=30}]}
分割區
特殊的分組,分為true和false兩組
//分成兩部分,一部分大於10歲,一部分小於等於10歲 Map<Boolean, List<User>> partMap = list.stream().collect(Collectors.partitioningBy(v -> v.getAge() > 20)); for (Map.Entry<Boolean, List<User>> entry : partMap.entrySet()) { System.out.println(entry.getKey() + "-->" + entry.getValue()); } //false-->[User{name='bb', age=20}, User{name='aa', age=20}] //true-->[User{name='dd', age=40}, User{name='aa', age=30}]
總結
Collector<T, A, R> 是一個介面,有以下5個抽象方法:
1.Supplier<A> supplier():建立一個結果容器A
2.BiConsumer<A, T> accumulator():消費型介面,第一個引數為容器A,第二個引數為流中元素T。
3.BinaryOperator<A> combiner():函數介面,該引數的作用跟上一個方法(reduce)中的combiner引數一樣,將並行流中各個子程序的執行結果(accumulator函數操作後的容器A)進行合併。
4.Function<A, R> finisher():函數式介面,引數為:容器A,返回型別為:collect方法最終想要的結果R。
5.Set<Characteristics> characteristics():返回一個不可變的Set集合,表明該Collector的特徵。有以下三個特徵:
注:如果對以上函數介面不太理解的話,可參考:Java中Lambda表示式的使用詳細教學
Collectors.toList() 解析
//toList 原始碼 public static <T> Collector<T, ?, List<T>> toList() { return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add, (left, right) -> { left.addAll(right); return left; }, CH_ID); } //為了更好地理解,我們轉化一下原始碼中的lambda表示式 public <T> Collector<T, ?, List<T>> toList() { Supplier<List<T>> supplier = () -> new ArrayList(); BiConsumer<List<T>, T> accumulator = (list, t) -> list.add(t); BinaryOperator<List<T>> combiner = (list1, list2) -> { list1.addAll(list2); return list1; }; Function<List<T>, List<T>> finisher = (list) -> list; Set<Collector.Characteristics> characteristics = Collections.unmodifiableSet (EnumSet.of(Collector.Characteristics.IDENTITY_FINISH)); return new Collector<T, List<T>, List<T>>() { @Override public Supplier supplier() { return supplier; } @Override public BiConsumer accumulator() { return accumulator; } @Override public BinaryOperator combiner() { return combiner; } @Override public Function finisher() { return finisher; } @Override public Set<Characteristics> characteristics() { return characteristics; } }; }
方法
方法 | 說明 |
---|---|
Optional<T> reduce(BinaryOperator<T> accumulator) | 第一次執行時,accumulator函數的第一個引數為流中的第一個元素,第二個引數為流中元素的第二個元素; 第二次執行時,第一個引數為第一次函數執行的結果,第二個引數為流中的第三個元素; 依次類推。 |
T reduce(T identity, BinaryOperator<T> accumulator) | 流程跟上面一樣,只是第一次執行時,accumulator函數的第一個引數為identity,而第二個引數為流中的第一個元素。 |
<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner) | 在序列流(stream)中,該方法跟第二個方法一樣,即第三個引數combiner不會起作用。在並行流(parallelStream)中,我們知道流被fork join出多個執行緒進行執行,此時每個執行緒的執行流程就跟第二個方法reduce(identity,accumulator)一樣,而第三個引數combiner函數,則是將每個執行緒的執行結果當成一個新的流,然後使用第一個方法reduce(accumulator)流程進行規約。 |
範例
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); Integer v = list.stream().reduce((x1, x2) -> x1 + x2).get(); System.out.println(v); // 15 Integer v0 = list.stream().reduce((x1, x2) -> x1 + x2).get(); System.out.println(v0); //15 Integer v1 = list.stream().reduce(10, (x1, x2) -> x1 + x2); System.out.println(v1); //25 Integer v2 = list.stream().reduce(0, (x1, x2) -> { System.out.println("stream accumulator: x1:" + x1 + " x2:" + x2); return x1 - x2; }, (x1, x2) -> { System.out.println("stream combiner: x1:" + x1 + " x2:" + x2); return x1 * x2; }); System.out.println(v2); // -15 Integer v3 = list.parallelStream().reduce(0, (x1, x2) -> { System.out.println("parallelStream accumulator: x1:" + x1 + " x2:" + x2); return x1 - x2; }, (x1, x2) -> { System.out.println("parallelStream combiner: x1:" + x1 + " x2:" + x2); return x1 * x2; }); System.out.println(v3); //-120
列印結果為:
15
15
25
stream accumulator: x1:0 x2:1
stream accumulator: x1:-1 x2:2
stream accumulator: x1:-3 x2:3
stream accumulator: x1:-6 x2:4
stream accumulator: x1:-10 x2:5
-15
parallelStream accumulator: x1:0 x2:3
parallelStream accumulator: x1:0 x2:5
parallelStream accumulator: x1:0 x2:4
parallelStream combiner: x1:-4 x2:-5
parallelStream accumulator: x1:0 x2:2
parallelStream accumulator: x1:0 x2:1
parallelStream combiner: x1:-3 x2:20
parallelStream combiner: x1:-1 x2:-2
parallelStream combiner: x1:2 x2:-60
-120
到此這篇關於一文詳解Java中Stream流的使用的文章就介紹到這了,更多相關Java Stream流內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45