1. 程式人生 > 程式設計 >5分鐘讓你快速掌握java8 stream常用開發技巧

5分鐘讓你快速掌握java8 stream常用開發技巧

前言

如果有些朋友以前沒有使用過java8 stream這種鏈式程式設計方式做開發,想學習一下。

如果有些朋友只學習了一部分用法,想學習更多。

如果有些朋友想看看有沒有好的示例適用於實際工作當中。

那麼恭喜你,這篇文章非常適合你。

首先,我們一起看看stream的繼承關係:

5分鐘讓你快速掌握java8 stream常用開發技巧

Stream、IntStream、LongStream、DoubleStream的父介面都是BaseStream。BaseStream的四個子介面方法都差不多,只是IntStream、LongStream、DoubleStream直接儲存基本型別,可以避免自動裝/拆箱,效率會更高一些。但是,我們實際上使用Stream更多一些。

我們再看看stream的工作流程圖:

5分鐘讓你快速掌握java8 stream常用開發技巧

為什麼要學stream的鏈式程式設計方式

業務需求1:指定一個字串陣列,找出裡面相同的元素,並且統計重複的次數。

我們以前大概是這樣做的:

public class CountTest {

 @Test
 public void testCount1() {
 List<String> list = Lists.newArrayList("a","b","ab","abc","a","abcd","bd","abc");

 Map<String,Long> countMap = new HashMap<>();
 for (String data : list) {
  Long aLong = countMap.get(data);
  if (Objects.isNull(aLong)) {
  countMap.put(data,1L);
  } else {
  countMap.put(data,++aLong);
  }
 }

 countMap.forEach((key,value) -> System.out.println("key:" + key + " value:" + value));
 }
}

執行結果:

key:a value:3
key:ab value:2
key:b value:1
key:bd value:1
key:abc value:2
key:abcd value:1

我們再看看如果用java8的stream可以怎麼做:

public class CountTest {

 @Test
 public void testCount2() {
 List<String> list = Lists.newArrayList("a","abc");
 Map<String,Long> countMap =  list.stream().collect(Collectors.groupingBy(Function.identity(),Collectors.counting()));
 countMap.forEach((key,value) ->  System.out.println("key:" + key + " value:" + value));
 }
}

執行結果:

key:a value:3
key:ab value:2
key:b value:1
key:bd value:1
key:abc value:2
key:abcd value:1

我們可以看到testCount1和testCount2執行結果相同,僅僅一行程式碼:

Map<String,Long> countMap = list.stream().collect(Collectors.groupingBy(Function.identity(),Collectors.counting()));

就可以實現上面testCount1中多行程式碼的邏輯。

業務需求2:從一個指定的字串陣列中,查詢指定的字串是否存在

我們以前大概是這樣做的:

public class FindTest {

 @Test
 public void testFind1() {
 String findStr = "bd";
 List<String> list = Lists.newArrayList("a","abc");
 boolean match = false;
 for (String data : list) {
  if (data.equals(findStr)) {
  match = true;
  break;
  }
 }
 //結果:match:true
 System.out.println("match:" + match);
 }
}

我們再看看如果用java8的stream可以怎麼做:

public class MatchTest {

 @Test
 public void testFind2() {
 String findStr = "bd";
 List<String> list = Lists.newArrayList("a","abc");
 boolean match = list.stream().anyMatch(x -> x.equals(findStr));
 //結果:match:true
 System.out.println("match:" + match);
 }
}

我們可以看到呼叫testFind1和testFind2方法執行結果也是一樣的。但是,用java8 stream的語法,又只用一行程式碼就完成功能了,真棒。

java8 stream超詳細用法指南

stream的操作符大體上分為兩種:中間操作符和終止操作符

中間操作:

1.filter(T-> boolean)

過濾資料,保留 boolean 為 true 的元素,返回一個集合

public class FilterTest {
 @Test
 public void testFilter() {
 List<Integer> list = Lists.newArrayList(20,23,25,28,30,33,37,40);
 //從指定資料集合中過濾出大於等於30的資料集合
 List<Integer> collect = list.stream().filter(x -> x >= 30).collect(Collectors.toList());
 //結果:[33,40]
 System.out.println(collect);
 }
}

collect(Collectors.toList())可以把流轉換為 List 型別,collect實際上是一個終止操作。

2.map(T -> R)

轉換操作符,可以做資料轉換,比如:把字串轉換成int、long、double,或者把一個實體轉換成另外一個實體。包含:map,mapToInt、mapToLong、mapToDouble

public class MapTest {


 @Test
 public void testMap() {
 List<String> list = Lists.newArrayList("1","2","3","4","5","6");
 List<Long> collect1 = list.stream().map(x -> Long.parseLong(x)).collect(Collectors.toList());
 //結果:[1,2,3,4,5,6]
 System.out.println(collect1);

 //結果:111111
 list.stream().mapToInt(x -> x.length()).forEach(System.out::print);
 System.out.println("");

 //結果:111111
 list.stream().mapToLong(x -> x.length()).forEach(System.out::print);
 System.out.println("");

 //結果:1.01.01.01.01.01.0
 list.stream().mapToDouble(x -> x.length()).forEach(System.out::print);
 }
}

3.flatMap(T -> Stream)

將流中的每一個元素 T 對映為一個流,再把每一個流連線成為一個流

public class FlatMapTest {

 @Test
 public void testFlatMap() {
 List<List<String>> list = new ArrayList<List<String>>(){{
  add(Lists.newArrayList("a","c"));
  add(Lists.newArrayList("d","e","f"));
  add(Lists.newArrayList("j","k","y"));
 }};
 //結果:[[a,b,c],[d,e,f],[j,k,y]]
 System.out.println(list);
 List<String> collect = list.stream().flatMap(List::stream).collect(Collectors.toList());
 //結果:[a,c,d,f,j,y]
 System.out.println(collect);
 }
}

我們可以看到flatMap可以輕鬆把字串的二維資料變成一位陣列。

4.distinct

去重,類似於msql中的distinct的作用,底層使用了equals方法做比較。

public class DistinctTest {

 @Test
 public void testDistinct() {
 List<String> list = Lists.newArrayList("a","abc");
 List<String> collect = list.stream().distinct().collect(Collectors.toList());
 //結果:[a,ab,abc,abcd,bd]
 System.out.println(collect);
 }
}

其實,去重還有另外一種辦法,可以用Collectors.toSet(),後面會講到。

5.sorted

對元素進行排序,前提是實現Comparable介面,當然也可以自定義比較器。

public class SortTest {

 @Test
 public void testSort() {
 List<Integer> list = Lists.newArrayList(5,7,1,6);
 List<Integer> collect = list.stream().sorted((a,b) -> a.compareTo(b)).collect(Collectors.toList());
 //結果:[1,6,7]
 System.out.println(collect);
 }
}

6.limit

限流操作,有點類似於mysql中的limit功能,比如:有10個元素,只取前面3個元素

public class LimitTest {

 @Test
 public void testLimit() {
 List<String> list = Lists.newArrayList("a","abc");
 List<String> collect = list.stream().limit(3).collect(Collectors.toList());
 //結果:[a,ab]
 System.out.println(collect);
 }
}

7.skip

跳過操作,比如:有個10個元素,從第5個元素開始去後面的元素

public class SkipTest {

 @Test
 public void testSkip() {
 List<String> list = Lists.newArrayList("a","abc");
 List<String> collect = list.stream().skip(5).collect(Collectors.toList());
 //結果:[ab,a,bd,abc]
 System.out.println(collect);
 }
}

8.peek

挑出操作,

public class PeekTest {
 @Test
 public void testPeek() {
 List<String> list = Lists.newArrayList("a","abc");
 //結果:abababcaabaabcdbdabc
 list.stream().peek(x -> x.toUpperCase()).forEach(System.out::print);
 }
}

眼尖的朋友會發現,進行x.toUpperCase()轉換為大寫功能,但是實際上沒有生效。把peek改成map方法試試:

public class PeekTest {
 @Test
 public void testPeek() {
 List<String> list = Lists.newArrayList("a","abc");
 //結果:ABABABCAABAABCDBDABC
 list.stream().map(x -> x.toUpperCase()).forEach(System.out::print);
 }
}

我們可以看到,用map操作轉換成大寫功能生效了,但是用peek操作卻沒有生效。peek只是對Stream中的元素進行某些操作,但是操作之後的資料並不返回到Stream中,所以Stream中的元素還是原來的元素。

終止操作:

1.forEach

遍歷操作,包含:forEach 和 forEachOrdered

forEach:支援並行處理

forEachOrdered:是按順序處理的,遍歷速度較慢

public class ForEachTest {

 @Test
 public void testForEach() {
 List<String> list = Lists.newArrayList("a","ab");
 //結果:a b ab
 list.stream().forEach(x-> System.out.print(x+' '));
 System.out.println("");

 //可以簡化
 //結果:a b ab
 list.forEach(x-> System.out.print(x+' '));
 System.out.println("");

 //結果:a b ab
 list.stream().forEachOrdered(x-> System.out.print(x+' '));
 }
}

2.collect

收集操作,將所有的元素收集起來,Collectors 提供了非常多收集器。包含:toMap、toSet、toList、joining,groupingBy,maxBy,minBy等操作。

toMap:將資料流轉換為map,裡面包含的元素是用key/value的形式的

toSet:將資料流轉換為set,裡面包含的元素不可重複

toList:將資料流轉出為list,裡面包含的元素是有序的

joining:拼接字串

groupingBy:分組,可以將list轉換map

couting:統計元素數量

maxBy:獲取最大元素

minBy:獲取最小元素

summarizingInt: 彙總int型別的元素,返回IntSummaryStatistics,再呼叫具體的方法對元素進行統計:getCount(統計數量),getSum(求和),getMin(獲取最小值),getMax(獲取最大值),getAverage(獲取平均值)

summarizingLong:彙總long型別的元素,用法同summarizingInt

summarizingDouble:彙總double型別的元素,用法同summarizingInt

averagingInt:獲取int型別的元素的平均值,返回一個double型別的資料

averagingLong:獲取long型別的元素的平均值,用法同averagingInt

averagingDouble:獲取double型別的元素的平均值,用法同averagingInt

mapping:獲取對映,可以將原始元素的一部分內容作為一個新元素返回

public class CollectTest {

 @Data
 @AllArgsConstructor
 class User {
  private String name;
  private Integer age;
 }


 @Test
 public void testCollect() {
  List<String> list0 = Lists.newArrayList("a","ab");
  Map<String,String> collect0 = list0.stream().collect(Collectors.toMap(String::new,Function.identity()));
  //結果:{ab=ab,a=a,b=b}
  System.out.println(collect0);

  List<String> list = Lists.newArrayList("a","ab");
  List<String> collect1 = list.stream().collect(Collectors.toList());
  //結果:[a,ab]
  System.out.println(collect1);

  //結果:[a,b]
  Set<String> collect2 = list.stream().collect(Collectors.toSet());
  System.out.println(collect2);

  String collect3 = list.stream().collect(Collectors.joining(","));
  //結果:a,ab
  System.out.println(collect3);

  Map<String,List<String>> collect4 = list.stream().collect(Collectors.groupingBy(Function.identity()));
  //結果:{ab=[ab,ab],a=[a,a],b=[b,b]}
  System.out.println(collect4);

  Long collect = list.stream().collect(Collectors.counting());
  //結果:6
  System.out.println(collect);

  String collect5 = list.stream().collect(Collectors.maxBy((a,b) -> a.compareTo(b))).orElse(null);
  //結果:b
  System.out.println(collect5);

  String collect6 = list.stream().collect(Collectors.minBy((a,b) -> a.compareTo(b))).orElse(null);
  //結果:a
  System.out.println(collect6);

  List<String> list2 = Lists.newArrayList("2","5");
  IntSummaryStatistics summaryStatistics = list2.stream().collect(Collectors.summarizingInt(x -> Integer.parseInt(x)));
  long sum = summaryStatistics.getSum();
  //結果:10
  System.out.println(sum);

  Double collect7 = list2.stream().collect(Collectors.averagingInt(x -> Integer.parseInt(x)));
  //結果:3.3333333333333335
  System.out.println(collect7);

  List<User> userList = new ArrayList<User>() {{
   add(new User("jack",23));
   add(new User("james",30));
   add(new User("curry",28));
  }};
  List<String> collect8 = userList.stream().collect(Collectors.mapping(User::getName,Collectors.toList()));
  //[jack,james,curry]
  System.out.println(collect8);
 }
}

3.find

查詢操作,包含:findFirst、findAny

findFirst:找到第一個,返回的型別為Optional

findAny:使用 stream() 時找到的是第一個元素,使用 parallelStream() 並行時找到的是其中一個元素,返回的型別為Optional

public class FindOpTest {

 @Test
 public void testFindOp() {
  List<String> list = Lists.newArrayList("a","bc","ab");
  //查詢第一匹配的元素
  String data1 = list.stream().findFirst().orElse(null);
  //結果: a
  System.out.println(data1);

  String data2 = list.stream().findAny().orElse(null);
  //結果: a
  System.out.println(data2);
 }
}

4.match

匹配操作,包含:allMatch、anyMatch、noneMatch

allMatch:所有元素都滿足條件,返回boolean型別

anyMatch:任意一個元素滿足條件,返回boolean型別

noneMatch:所有元素都不滿足條件,返回boolean型別

public class MatchTest {

 @Test
 public void testMatch() {
  List<Integer> list = Lists.newArrayList(2,7);
  boolean allMatch = list.stream().allMatch(x -> x > 1);
  //結果:true
  System.out.println(allMatch);

  boolean allMatch2 = list.stream().allMatch(x -> x > 2);
  //結果:false
  System.out.println(allMatch2);

  boolean anyMatch = list.stream().anyMatch(x -> x > 2);
  //結果:true
  System.out.println(anyMatch);

  boolean noneMatch1 = list.stream().noneMatch(x -> x > 5);
  //結果:false
  System.out.println(noneMatch1);

  boolean noneMatch2 = list.stream().noneMatch(x -> x > 7);
  //結果:true
  System.out.println(noneMatch2);
 }
}

5.count

統計操作,效果跟呼叫集合的size()方法類似

public class CountOpTest {

 @Test
 public void testCountOp() {
  List<String> list = Lists.newArrayList("a","ab");
  long count = list.stream().count();
  //結果:3
  System.out.println(count);
 }
}

6.min、max

min:獲取最小值,返回Optional型別的資料

max:獲取最大值,返回Optional型別的資料

public class MaxMinTest {

 @Test
 public void testMaxMin() {
  List<Integer> list = Lists.newArrayList(2,7);
  Optional<Integer> max = list.stream().max((a,b) -> a.compareTo(b));
  //結果:7
  System.out.println(max.get());

  Optional<Integer> min = list.stream().min((a,b) -> a.compareTo(b));
  //結果:2
  System.out.println(min.get());
 }
}

7.reduce

規約操作,將整個資料流的值規約為一個值,count、min、max底層就是使用reduce。

reduce 操作可以實現從Stream中生成一個值,其生成的值不是隨意的,而是根據指定的計算模型。

public class ReduceTest {

 @Test
 public void testReduce() {
  List<Integer> list = Lists.newArrayList(2,7);
  Integer sum1 = list.stream().reduce(0,Integer::sum);
  //結果:17
  System.out.println(sum1);

  Optional<Integer> reduce = list.stream().reduce((a,b) -> a + b);
  //結果:17
  System.out.println(reduce.get());

  Integer max = list.stream().reduce(0,Integer::max);
  //結果:7
  System.out.println(max);

  Integer min = list.stream().reduce(0,Integer::min);
  //結果:0
  System.out.println(min);


  Optional<Integer> reduce1 = list.stream().reduce((a,b) -> a > b ? b : a);
  //2
  System.out.println(reduce1.get());
 }
}

8.toArray

陣列操作,將資料流的元素轉換成陣列。

public class ArrayTest {

 @Test
 public void testArray() {
  List<String> list = Lists.newArrayList("a","ab");
  String[] strings = list.stream().toArray(String[]::new);
  //結果:a b ab
  for (int i = 0; i < strings.length; i++) {
   System.out.print(strings[i]+" ");
  }
 }
}

stream和parallelStream的區別

stream:是單管道,稱其為流,其主要用於集合的邏輯處理。

parallelStream:是多管道,提供了流的並行處理,它是Stream的另一重要特性,其底層使用Fork/Join框架實現

public class StreamTest {

 @Test
 public void testStream() {
  List<Integer> list = Lists.newArrayList(1,7);
  //結果:1234567
  list.stream().forEach(System.out::print);
 }
}
public class ParallelStreamTest {
 @Test
 public void testParallelStream() {
  List<Integer> list = Lists.newArrayList(1,7);
  //結果:5726134
  list.parallelStream().forEach(System.out::print);
 }
}

我們可以看到直接使用parallelStream的forEach遍歷資料,是沒有順序的。

如果要讓parallelStream遍歷時有順序怎麼辦呢?

public class ParallelStreamTest {

 @Test
 public void testParallelStream() {
  List<Integer> list = Lists.newArrayList(1,7);
  //結果:1234567
  list.parallelStream().forEachOrdered(System.out::print);
 }
}

parallelStream的工作原理:

5分鐘讓你快速掌握java8 stream常用開發技巧

實際工作中的案例

1.從兩個集合中找相同的元素。一般用於批量資料匯入的場景,先查詢出資料,再批量新增或修改。

public class WorkTest {

 @Test
 public void testWork1() {
  List<String> list1 = Lists.newArrayList("a","ab");
  List<String> list2 = Lists.newArrayList("a","c","ab");
  List<String> collect = list1.stream()
    .filter(x -> list2.stream().anyMatch(e -> e.equals(x)))
    .collect(Collectors.toList());
  //結果:[a,ab]
  System.out.println(collect);

 }
}

2.有兩個集合a和b,過濾出集合a中有,但是集合b中沒有的元素。這種情況可以使用在假如指定一個id集合,根據id集合從資料庫中查詢出資料集合,再根據id集合過濾出資料集合中不存在的id,這些id就是需要新增的。

@Test
public void testWork2() {
 List<String> list1 = Lists.newArrayList("a","ab");
 List<String> list2 = Lists.newArrayList("a","ab");
 List<String> collect = list1.stream()
  .filter(x -> list2.stream().noneMatch(e -> e.equals(x)))
  .collect(Collectors.toList());
 //結果:[b]
 System.out.println(collect);
}

3.根據條件過濾資料,並且去重做資料轉換

 @AllArgsConstructor
 @Data
 class User {
  private String name;
  private Integer age;
 }

 @Test
 public void testWork3() {
  List<User> userList = new ArrayList<User>() {{
   add(new User("jack",28));
   add(new User("tom",27));
   add(new User("sue",29));
  }};

  List<String> collect = userList.stream()
    .filter(x -> x.getAge() > 27)
    .sorted((a,b) -> a.getAge().compareTo(b.getAge()))
    .limit(2)
    .map(User::getName)
    .collect(Collectors.toList());
  //結果:[curry,sue]
  System.out.println(collect);
 }

4.統計指定集合中,姓名相同的人中年齡最小的年齡

@Test
public void testWork4() {
 List<User> userList = new ArrayList<User>() {{
  add(new User("tom",23));
  add(new User("james",30));
  add(new User("james",28));
  add(new User("tom",27));
  add(new User("sue",29));
 }};

 userList.stream().collect(Collectors.groupingBy(User::getName))
   .forEach((name,list) -> {
    User user = list.stream().sorted((a,b) -> a.getAge().compareTo(b.getAge())).findFirst().orElse(null);
    //結果:name:sue,age:29
    //  name:tom,age:23
    //  name:james,age:28
    System.out.println("name:" + name + ",age:" + user.getAge());
   });
}

總結

到此這篇關於java8 stream常用開發技巧的文章就介紹到這了,更多相關java8 stream常用開發技巧內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!