Stream實戰

一、Stream(流)是什麼?

  • Stream即流,在Java 8中引入,它與java.io包裏的InputStream與OutputStream是徹底不一樣的概念。簡單來說流是一個包含着一系列數據項的集合,但它又不是一個存儲容器,更像是一個與數據項相關的算法容器。
  • Scala和Groovy證實了,函數做爲一等公民能夠大大擴充程序員的工具庫。在Stream中也使用了函數式編程的思想,極大地提升和解放了生產力。熟練使用Stream強大的功能,你能寫出更加簡潔、表現力更強的代碼,今後告別996。

二、 Stream 的構造

    1. 經過集合的stream()或者parallelStream();
    1. 流的靜態方法,如Stream.of();
    1. 隨機數流Random.ints();
    1. 包裝類型在Stream中經常使用,可是其實基本類型也能使用Stream。如定義基本類型的Stream,可使用IntStream,LongStream,DoubleStream的接口靜態方法of,range,empty
    1. 從文件等輸入流中構造
//1.集合中構造
	Arrays.asList(1,2,3,4,5).stream()...;

//2.靜態構造
	Stream.of(1,2,3,4,5)...
 
//3.隨機數流
	//IntStream
 	new Random(100).ints();
	IntStream.of(1,2,3);
  IntStream.range(1,100);
	//LongStream
  new Random(100).longs();
	//DoubleStream
	new Random(100).doubles();

//4.IntStream/LongStream/DoubleStream
//也可使用Stream<Integer>、Stream<Long>、Stream<Double>構造
	IntStream.of(new int[]{1,2,3});
	
//5.文件輸入構造
//一般Stream不須要關閉,僅僅是須要關閉在IO通道上運行的流

	try(final Stream<String> lines=Files.lines(Paths.get("somePath"))){
    lines.forEach(System.out::println);
	}
複製代碼

三、 Stream工做流程

  • Stream相似於一個迭代器,能夠對流中每一個元素迭代處理。串行化的處理與迭代器(Itrerator)相似,可是Stream的功能遠不止迭代這麼簡單。java

  • 其中的中間操做、終端操做。一般小夥伴在stream處理中一頓操做後,發現IDE爆紅,常常不太明白什麼緣由,不少狀況下是不明白中間操做與終端操做。程序員

  • 簡單來說,中間操做執行後會返回一個流,相似於builder設計模式中build()後一般會有return this這麼一個操做,中間操做返回了一個處理流從而提供了鏈式調用語法。而終端操做就是一個收尾操做,通常返回void或者非stream結果。如咱們經常使用的toList()、toSet()、toMap()、toArray就是非stream結果,只有反作用的 foreach() 也是void。算法

  • map、flatmap、filter、peek、limit、skip、distinct、sorted...都是中間操做,foreach、forEachOrdered、collect、findFirst、min、max則是終端操做。編程

3.一、中間操做

3.1.一、map

  • 做用於stream中的每一個元素
//下面兩種等價的方式,完成將字符串轉大寫並排序
//1.函數式接口方式
	()->stream.of("apple","banana","orange","grapes", "melon","blueberry","blackberry")
	          .map(String::toUpperCase)
	          .sorted();

//2.Lambda表達式方式
	()->stream.of("apple","banana","orange","grapes", "melon","blueberry","blackberry")
	          .map(v->v.toUpperCase())
	          .sorted();
複製代碼

3.1.二、flatmap

  • 與map相似,都是一個函數做用於stream中的每一個元素。設計模式

  • 從函數簽名能夠看出:map返回值是一個對象,對象造成了一個新的Stream。而flatmap返回的是一個Stream。flatmap不會在建立一個新Stream,而是將原來元素轉換爲Stream。一般用於流的扁平化處理數組

    //map()簽名
    	<R> Stream<R> map(Function<? super T, ? extends R> mapper);
    
    //flatmap()簽名
    	<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
    
    //flatmap返回Stream
    	Stream.of(1,22,33).flatMap(v->Stream.of(v*v)).collect(Collectors.toList());
    
    //map返回對象
    	Stream.of(1,22,33).map(v->v*v).collect(Collectors.toList());
    
    //flatMap的扁平化處理
    
      List<Map<String, String>> list = new ArrayList<>();
      Map<String,String> map1 = new HashMap();
      map1.put("1", "one");
      map1.put("2", "two");
    
      Map<String,String> map2 = new HashMap();
      map2.put("3", "three");
      map2.put("4", "four");
      list.add(map1);
      list.add(map2);
    
      Set<String> output= list.stream()  // Stream<Map<String, String>>
          .map(Map::values)              // Stream<List<String>>
          .flatMap(Collection::stream)   // Stream<String>
          .collect(Collectors.toSet());  //Set<String>
      [one, two, three,four]
    
    複製代碼

3.1.三、peek

  • peek也是對流中的每個元素進行操做,除了生成一個包含原全部元素的新Stream,還提供一個Consumer消費函數。app

  • 與map對比可看出peek在流處理中,能夠作一些輸出、外部處理、反作用等無返回值。生成一個包含原Stream的全部元素的新Stream,新Stream每一個元素在被消費以前都會執行peek給定的消費函數;dom

    //對每個元素進行一些反作用
    	List<Integer> list = new ArrayList();
    	List<Integer> result = Stream.of(1, 2, 3, 4)
           .peek(x -> list.add(x))
           .map(x -> x * 2)
           .collect(Collectors.toList());
    
    //1
    //2
    //3
    //[1, 2, 3]
     System.out.println(list);
    
    
    //map()簽名,返回值R
    	<R> Stream<R> map(Function<? super T, ? extends R> mapper);
    //peek()簽名,返回值void
    	Stream<T> peek(Consumer<? super T> action); 
    複製代碼

3.1.四、filter

  • 設置過濾條件,知足filter的元素彙總生成一個新的stream
//篩選出 >0 的元素
	Arrays.asList(1,2,3,4,5)
    		.stream()
    		.filter(v-> v>0)
    		.toArray(Integer[]::new);

//篩選出字母A開頭的字符串
	Stream.of("apple","banana","orange","grapes", "melon","blueberry","blackberry")
            .filter(s->s.startWith("A"))
  	    .forEach(System.out::println)
複製代碼

3.1.五、limit/skip

  • limit返回Stream前面幾個元素,skip扔掉前面幾個元素。

3.1.六、distinct

  • 簡單stream的去重ide

    //去重
    Stream.of(1,2,3,3,3,2,4,5,6)
      .distinct()
      .collect(Collectors.toSet());
    複製代碼

3.二、終端操做

3.2.一、findFirst

  • 它老是返回 Stream 的第一個元素,或者空。注意返回值是Optional。函數式編程

  • Optional可能含有值,也可能不含,主要是儘量避免NPE。

Optional<String> ops = Stream.of("apple","banana","orange","blueberry","blackberry")
	                .filter(s->s.startsWith("b"))
	                .findFirst();
//banana
ops.orElse("apple");

Optional<String> ops = Stream.of("apple","banana","orange","blueberry","blackberry")
                    .filter(s->s.startsWith("c"))
                    findFirst();
//apple
ops.orElse("apple");
複製代碼

3.2.二、強大的collect

也許不少人常常搞不清Collector、Collection、Collections、Collectors。
1.Collection是Java集合祖先接口;
2.Collections是java.util包下的一個工具,內含有各類處理集合的靜態方法。
3.java.util.stream.Stream#collect(java.util.stream.Collector)是Stream的一個函數,負責收集流。
4.java.util.stream.Collector是一個收集函數的接口,聲明一個收集器功能。
5.java.util.Collectors是一收集器的工具類,內置了一系列經常使用收集器的實現,如Collectors.toList()/toSet(),做爲上述第3條的collect()參數。
  • toList/toMap

    //toList
    //方式1
    List<String> list = Stream.of("I","love","you","too")
      		.collect(ArrayList::new,ArrayList::add,ArrayList::addAll);
    	//方式2
    	List<String> list = stream.collect(Collections.toList())
             
    //toMap 
      Map<Integer, Integer> collect1 = Stream.of(1, 3, 4)
    				.collect(Collectors.toMap(x -> x, x -> x + 1));
        
    複製代碼
  • 數組轉List

    List list = new ArrayList<>(Arrays.asList("a", "b", "c"));
    Integer [] myArray = { 1, 2, 3 };
    List myList = Arrays.stream(myArray).collect(Collectors.toList());
    //基本類型也能夠實現轉換(依賴boxed的裝箱操做)
    int [] myArray2 = { 1, 2, 3 };
    List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
    複製代碼
  • 也許collect是咱們最經常使用的一個終端操做,toList、toSet、toMap一鼓作氣。可是若是看collect函數簽名會發現,這個collect不簡單。

//collect1
<R> R collect( Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner) //collect2 /** @param1: supplier爲結果存放容器 @param2: accumulator爲結果如何添加到容器的操做 @param3: combiner則爲多個容器的聚合策略 */ <R,A> R collect(collector<? super T,A,R> collector);

複製代碼
  • 從函數簽名中可看出其實咱們只用到了collect的第二個方法,即便用jdk提供的Collector的toList、toSet、toMap。正在由於這些操做經常使用,故jdk中直接提供了這些collector。

  • 咱們也能夠實現本身的Collector

    /* T:流中要收集的對象的泛型 A:累加器的類型,累加器是在收集過程當中用於累加部分結果的對象 R:收集操做獲得的對象(一般但不必定是集合)的類型。 */
    public interface Collector<T,A,R> {
    
    	//結果容器
        Supplier<A> supplier();
    
    	//累加器執行累加的具體實現
        BiConsumer<A, T> accumulator();
    
        //合併2個結果的容器
        BinaryOperator<A> combiner();
    
        //對結果容器應用最終轉換finisher
        Function<A, R> finisher();
    
        //characteristics
        Set<Characteristics> characteristics();
    }
    
    
    //自定義Collector
    //1.創建新的結果容器supplier(),返回值必須是一個空Supplier,供數據收集過程使用
    
      //toList返回一個空List<>,toSet、toMap相似
          @Override
          public Supplier<List<T>> supplier() {
              return ArrayList::new;
          }
    
    //2.累加器執行累加的具體實現accumulator()
    //從BiConsumer看出返回值void,接受2個參數第一個是累計值,第二個是當期處理的第n個元素
          @Override
        	public BiConsumer<List<T>, T> accumulator() {
            //能夠看出是個拼接list的操做
              return List::add;
        	}
    	
    	//3.轉換最終結果容器的finisher()
    	//流遍歷完成後,有時須要對結果處理,能夠藉助finisher。finisher()須返回累加過程當中的最後一個調用函數,用於將累加器對象轉換爲集合。 
    	//接收2個參數,第一個參數是累加值,第二個參數是返回值,返回值就是咱們最終要的東西。
    	@Override
    	public Function<List<T>, List<T>> finisher() {
             //原樣輸出不額外處理
             //其實就是Function.identity()
        	    return (i) -> i;
    	}
    
    	//4.合併容器的combiner()
    	//Stream支持並行操做,但並行的子部分處理規約如何處理?combiner()就是指明瞭各個子任務如何合併。
    	@Override
    	public BinaryOperator<List<T>> combiner() {	
            //每一個子任務是一個List,2個子任務結果合併累加到第一個子任務上
            return (list1, list2) -> {
            	list1.addAll(list2);
    	        return list1;
        	    };
    	}
    
    	//5.characteristics()
    	
    複製代碼
  • 字符串的toList的Collector實現,核心是理解T、A、R

public class MyCollector<String> implements Collector<String, List<String>, List<String>> {
    @Override
    public Supplier<List<String>> supplier() {
      return ArrayList::new;
    }

    @Override
    public BiConsumer<List<String>, String> accumulator() {
      return (List<String> l, String s) -> {
        l.add(s);
      };
    }

    @Override
    public BinaryOperator<List<String>> combiner() {
      return (List<String> l, List<String> r) -> {
        List<String> list = new ArrayList<>(l);
        list.addAll(r);
        return list;
      };
    }

    @Override
    public Function<List<String>, List<String>> finisher() {
      return Function.identity();
    }

    @Override
    public Set<Characteristics> characteristics() {
      return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH));
    }
  }



  Stream<String> apple = Stream
        .of("apple","banana", "orange", "grapes", "melon", "blueberry", "blackberry");

  System.out.println(apple.collect(new MyCollector<>()));



//字符串的拼接concat

String concat = Stream
        .of("apple", "apple","banana", "orange", "grapes", "melon", "berry", 	"blary")
  			.collect(StringBuilder::new,
  					 StringBuilder::append,
  					 StringBuilder::append)
  			.toString();
//等價於上面,這樣看起來應該更加清晰
String concat = stringStream.collect(() -> new StringBuilder(),(l, x) -> l.append(x), (r1, r2) -> r1.append(r2)).toString();

複製代碼

3.2.三、reduce操做

  • 這個方法的主要做用是把stream元素組合起來。它提供一個初始值(種子),而後將運算規則(BinaryOperator),和前面Stream的第一個、第二個、第n個元素組合。從這個意義上來說,字符串拼接、數組的sum、min、max、average都是特殊的reduce。
Integer sum = integers.reduce(0,(a,b)->a+b);
或者
Integer sum = integers.reduce(0,Integer::sum);

複製代碼
  • 沒有起始值的狀況下,會把Stream前面2個元素組合起來,返回的是Optional( 因爲沒有起始的reduce(),故可能沒有足夠的元素,所以設計返回的是Optional)
//原生操做
final Integer[] integers = Lists.newArrayList(1, 2, 3, 4, 5)
        .stream()
        .collect(() -> new Integer[]{0}, (a, x) -> a[0] += x, (a1, a2) -> a1[0] += a2[0]);
//reducing操做
final Integer collect = Lists.newArrayList(1, 2, 3, 4, 5)
        .stream()
        .collect(Collectors.reducing(0, Integer::sum));    
//固然Stream也提供了reduce操做
final Integer collect = Lists.newArrayList(1, 2, 3, 4, 5)
        .stream().reduce(0, Integer::sum)
複製代碼
  • reduce拼接字符串
String concat = Stream.of("A","B","C","D")
  							.reduce("",String::concat);
複製代碼
  • reduce求最小值
double minValue = Stream.of(-1.5,1.0,-3.0,-2.0)
  								.reduce(Double.MAX_VALUE,Double::min);
複製代碼
  • reduce求和
//有起始值
int sumValue = Stream.of(1,2,3,4)
  						.reduce(0,Integer::sum);

//無起始值
int sumValue = Stream.of(1,2,3,4)
  						.reduce(Integer::sum).get();
複製代碼

四、開發中經常使用的

4.一、 本身生成流

  • 經過實現Supplier接口,能夠本身控制流的生成。
  • 把 Supplier 實例傳遞給 Stream.generate() 生成的 Stream,默認是串行(相對 parallel 而言)但無序的(相對 ordered 而言)。因爲它是無限的,在管道中,必須利用 limit 之類的操做限制 Stream 大小。
Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);
//Another way
IntStream.generate(() -> (int) (System.nanoTime() % 100)).
limit(10).forEach(System.out::println);

複製代碼

4.2 、生成等差數列

//步長爲3
Stream.iterate(0, n -> n + 3)
  .limit(10)
  .forEach(x -> System.out.print(x + " "));
複製代碼

4.三、 reduce、collect實現filter功能

 1.collect實現方式更簡單,效率也更高。
 2.reduce每次須要new ArrayList是由於reduce規定第二個參數:
    BiFunction accumulator表達式不能改變其自身參數acc原有值,因此每次都要new ArrayList(acc),再返回新的list。
//reduce 方式
public static <T> List<T> filter(Stream<T> stream,Predicate<T> predicate){
    
    return stream.reduce(new ArrayList<T>(),(acc,t)->{
        if(predicate.test(t)){
            List<T> lists = new ArrayList<T>(acc);
            lists.add(t);
            return lists;
        }
        return acc;
    }, (List<T> left,List<T> right)->{
        List<T> lists= new ArrayList<T>(left);
        lists.addAll(right);
        return lists;
    }
}


//collect

public static <T> List<T> filter(Stream<T> stream, Predicate<T> predicate) {
   return stream.collect(ArrayList::new, (acc, t) -> {
       if (predicate.test(t))
           acc.add(t);
   }, ArrayList::addAll);
}

複製代碼

4.四、 返回規約類型

//使用toCollection()指定規約容器的類型
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));// (3)
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));// (4)

複製代碼

4.五、 groupingBy上下游收集器

  • 使用partitioningBy()生成的收集器,適用於將Stream中的元素依據某個二值邏輯(知足、不知足)分紅互補相交的兩部分,如性吧,及格與否
  • groupingBy()按照某個屬性對數據分組,屬性相同的元素會被對應到Map的一個key上
//partitioningBy()
Map<Boolean, List<Student>> passingFailing = students.stream()
         .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
         
//groupingBy
Map<Department, List<Employee>> byDept = employees.stream()
            .collect(Collectors.groupingBy(Employee::getDepartment));       


//mapping的下游
//按照部門對員工進行分組,而且只保留員工的名字
Map<Department,List<String>> byDept=employees.stream()
            .collect(Collectors.groupingBy(Employee::getDepartment,
                Collectors.mapping(Employee::getName,   //下游收集器
                    Collectors.toList())));             //更下游收集器

複製代碼
相關文章
相關標籤/搜索