Stream流

前言

這篇詳細(xì)介紹了Stream流的概念巩趁,創(chuàng)建方式,基本操作及部分源碼分析淳附∫槲浚可能有點(diǎn)長(zhǎng)哈,大家看起來(lái)比較費(fèi)勁奴曙,我自己寫(xiě)的也比較累别凹,光碼字就碼了很長(zhǎng)時(shí)間,大家看得過(guò)程中可以停下來(lái)休息下洽糟,喝個(gè)茶炉菲,斗個(gè)地主接著再來(lái)看,就安利到這里了坤溃,我也該洗洗睡了(碼字確實(shí)挺累的拍霜,寫(xiě)文章也是很累),最后在安利下哈薪介,個(gè)人絕得很詳細(xì)的祠饺,祝大家學(xué)習(xí)愉快哈,看完就可以找到心儀小姐姐(小哥哥)汁政。

1.流的概述及相關(guān)概念
java8提供了Stream API道偷,以流的方式來(lái)處理數(shù)據(jù)。那么到底什么是jdk8中所謂的流呢记劈,簡(jiǎn)單地講勺鸦,就是對(duì)數(shù)組以及集合等數(shù)據(jù)進(jìn)行加工處理,比如對(duì)數(shù)據(jù)過(guò)濾目木,映射祝旷,遍歷等操作。Stream流不是一種數(shù)據(jù)結(jié)構(gòu),不存儲(chǔ)數(shù)據(jù)怀跛,通過(guò)管道的方式獲取數(shù)據(jù)距贷,然后對(duì)數(shù)據(jù)進(jìn)行加工處理,并不會(huì)修改底層的數(shù)據(jù)源的吻谋。那么為什么要使用流呢忠蝗?因?yàn)槭褂昧骱蚻ambada表達(dá)式(流常與lambada表達(dá)式和函數(shù)式接口一起使用)對(duì)數(shù)據(jù)進(jìn)行操作的時(shí)候更加簡(jiǎn)潔,簡(jiǎn)單漓拾,同時(shí)流能利用現(xiàn)代多核CPU的特性妆棒,提高并行并發(fā)效率遮咖。流由以下三部分構(gòu)成:

  • 源(集合,數(shù)組等數(shù)據(jù)源)
  • 零個(gè)或多個(gè)中間操作,如filter狡逢,map等硕蛹,中間操作可以看做是流水線操作掺栅,每一個(gè)中間操作都可看做是一條流水線冗澈。中間操作執(zhí)行完后會(huì)產(chǎn)生一個(gè)新流,原來(lái)的數(shù)據(jù)源沒(méi)有變化示血。
  • 終止操作棋傍,如forEach,count等,終止操作不會(huì)返回Stream類(lèi)型难审,可能不返回值瘫拣,也可能返回其他類(lèi)型的單個(gè)值

流操作的分類(lèi)

  • 惰性求值,對(duì)應(yīng)于流的中間操作告喊,執(zhí)行后不直接產(chǎn)生結(jié)果
  • 及早求值麸拄,對(duì)應(yīng)于流的終止操作,執(zhí)行后產(chǎn)生結(jié)果

舉個(gè)栗子:

public class StreamTest02 {

   public static void main(String[] args) {
       List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
       //將list中的每個(gè)數(shù)乘以2黔姜,然后在求和
       final Stream<Integer> integerStream = list.stream().map(i -> {
           System.out.println("test----------------------------------");
           return i * 2;
       });
       System.out.println(integerStream);
   }
}
執(zhí)行結(jié)果

以上集合list就是Stream流的源拢切,map操作就是中間操作,對(duì)應(yīng)惰性求值地淀,如果后面沒(méi)有終止操作失球,map操作只是返回一個(gè)新的流岖是,但是并不執(zhí)行map中的操作帮毁,更看不到結(jié)果。想要map中的操作執(zhí)行豺撑,需要在后面加上終止操作烈疚。

public class StreamTest02 {
   public static void main(String[] args) {
       List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
       //將list中的每個(gè)數(shù)乘以2,然后在求和
        list.stream().map(i -> {
           System.out.println("test-----------------------------");
           return i * 2;
       }).reduce(Integer::sum);
   }
}
執(zhí)行結(jié)果

以上的reduce操作就是終止操作聪轿,對(duì)應(yīng)及早求值爷肝,執(zhí)行reduce終止操作時(shí),才一下子把所有的中間操作都執(zhí)行了。這時(shí)我們發(fā)現(xiàn)map中的操作執(zhí)行了灯抛,但是reduce歸約求和后的結(jié)果任然看不見(jiàn)金赦,這是因?yàn)槲覀儧](méi)有打印輸出結(jié)果,輸出就好了:

public class StreamTest02 {

   public static void main(String[] args) {
       List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
       //將list中的每個(gè)數(shù)乘以2对嚼,然后在求和
        list.stream().map(i -> {
           System.out.println("test-----------------------------");
           return i * 2;
       }).reduce(Integer::sum).ifPresent(System.out::println);
   }
}
執(zhí)行結(jié)果

流的分類(lèi):

  • 串行流:stream
  • 并行流:parallelStream

這里大致比較一下并行流和串行流的執(zhí)行效率:

1.使用串行流:

public class StreamTest{

   public static void main(String[] args) {

       List<String> list = new ArrayList<>(10000000);
       for (int i = 0; i < 10000000; i++) {
           list.add(UUID.randomUUID().toString());
       }
       System.out.println("開(kāi)始排序...");
       long startTime = System.nanoTime();
       list.stream().sorted().count();
       long endTime = System.nanoTime();
       long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
       System.out.println("排序耗時(shí)為:" + millis);
   }
}
執(zhí)行結(jié)果

2.使用并行流:

public class StreamTes{

   public static void main(String[] args) {

       List<String> list = new ArrayList<>(10000000);
       for (int i = 0; i < 10000000; i++) {
           list.add(UUID.randomUUID().toString());
       }
       System.out.println("開(kāi)始排序...");
       long startTime = System.nanoTime();
       list.parallelStream().sorted().count();
       long endTime = System.nanoTime();
       long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
       System.out.println("排序耗時(shí)為:" + millis);
   }
}
執(zhí)行結(jié)果

同樣是對(duì)一千萬(wàn)數(shù)進(jìn)行排序并統(tǒng)計(jì)元素個(gè)數(shù)夹抗,使用并行流要比使用串行流快了不少。是因?yàn)椴⑿辛鞒浞掷矛F(xiàn)代CPU的多核特性纵竖,使用多個(gè)線程并行地執(zhí)行任務(wù)漠烧,極大地提高了CPU的利用率,也提高了我們程序的執(zhí)行效率靡砌。

除了以上使用Collection接口中的stream和parallelStream方法來(lái)獲得并行流外已脓,還可以對(duì)流調(diào)用parallel()方法,parallel()是BaseStream接口中的一個(gè)方法通殃,源碼如下:

S parallel();

BaseStream接口中還提供了sequential()方法度液,表示一個(gè)串行流,使流能在串行流和并行流之間切換邓了。
提一下恨诱,Stream和BaseStream的關(guān)系是父子關(guān)系,Stream繼承了BaseStream接口骗炉。

栗子:

@Test
public void test(){
    students.stream().parallel().filter(sudent ->sudent.getAge() > 16).limit(2).forEach(System.out::println);
}

如果不顯示聲明為parallel照宝,默認(rèn)是串行流。

注意:

  • 流自己不存儲(chǔ)數(shù)據(jù)元素
  • 流不改變數(shù)據(jù)源句葵,相反厕鹃,會(huì)返回一個(gè)持有結(jié)果的新的Stream
  • 流的操作時(shí)延遲執(zhí)行的,需要結(jié)果的時(shí)候才執(zhí)行操作乍丈。

2.流的創(chuàng)建方式

2.1通過(guò)Stream接口的靜態(tài)方法of創(chuàng)建流剂碴,of方法源碼如下:

public static<T> Stream<T> of(T... values) {
   return Arrays.stream(values);
}

栗子:

Stream<String> stream = Stream.of("beijing", "shanghai", "tianjin");

2.2通過(guò)數(shù)組的方式創(chuàng)建流

String[] arrays = new String[]{"beijing", "shanghai", "tianjin"};
Stream stringStream = Arrays.stream(arrays);

2.3通過(guò)集合方式創(chuàng)建流:

String[] arrays = new String[]{"beijing", "shanghai", "tianjin"};
List<String> list = Arrays.asList(arrays);
Stream stream = list.stream();

2.4通過(guò)Sream的迭代方法iterate創(chuàng)建無(wú)限流

iterate方法源碼分析:

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {...}

通過(guò)源碼可知,iterate方法有兩個(gè)參數(shù)轻专,第一個(gè)種子參數(shù)seed忆矛,表示從哪開(kāi)始,第一個(gè)要迭代的元素是哪個(gè)请垛。地二個(gè)參數(shù)是個(gè)函數(shù)式接口類(lèi)型的操作參數(shù)催训,UnaryOperator源碼如下:

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {  
   static <T> UnaryOperator<T> identity() {
       return t -> t;
   }
}

UnaryOperator繼承于Function,給定一個(gè)參數(shù),返回結(jié)果宗收,不過(guò)UnaryOperator的參數(shù)和返回值類(lèi)型一樣都是T類(lèi)型漫拭。

舉個(gè)栗子:

Stream<Integer> integerStream = Stream.iterate(0,i -> i + 2);
//這里執(zhí)行一下終止操作forEach,遍歷下結(jié)果混稽,驗(yàn)證這是個(gè)無(wú)限流
integerStream.forEach(System.out::println);
執(zhí)行結(jié)果

以上創(chuàng)建的無(wú)限流如果不手動(dòng)停止運(yùn)行采驻,就會(huì)一直無(wú)限地執(zhí)行下去审胚,直到內(nèi)存爆滿(mǎn)異常退出。
如果要獲取有限個(gè)數(shù)的結(jié)果礼旅,可以加一個(gè)限制個(gè)數(shù)的中間操作limit操作:

Stream<Integer> integerStream = Stream.iterate(0,i -> i + 2);
integerStream.limit(10).forEach(System.out::println);
執(zhí)行結(jié)果

2.5通過(guò)Stream的generate靜態(tài)方法生成一個(gè)流

public static<T> Stream<T> generate(Supplier<T> s) {...}

由源碼可知膳叨,參數(shù)是一個(gè)Supplier類(lèi)型的函數(shù)式接口,Supplier不傳入任何參數(shù)痘系,但返回結(jié)果懒鉴。

栗子:

Stream.generate(Math::random).limit(10).forEach(System.out::println);

以上列出了創(chuàng)建Stream的五種方式,但Stream的創(chuàng)建不限于以上五種方法碎浇。

3.流的基本操作及源碼解析

Student類(lèi):

public class Student{
   private String name;
   private Integer age;
   private double score;
   private String level;

   public String getLevel() {
       return level;
   }

   public void setLevel(String level) {
       this.level = level;
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public Integer getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }

   public double getScore() {
       return score;
   }

   public void setScore(double score) {
       this.score = score;
   }

   public Student(String name, Integer age, double score, String level) {
       this.name = name;
       this.age = age;
       this.score = score;
       this.level = level;
   }

   public Student() {
   }

   @Override
   public String toString() {
       return "Student{" +
               "name='" + name + '\'' +
               ", age=" + age +
               ", score=" + score +
               ", level='" + level + '\'' +
               '}';
   }

   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Student student = (Student) o;
       return age == student.age &&
               Double.compare(student.score, score) == 0 &&
               Objects.equals(name, student.name);
   }

   @Override
   public int hashCode() {
       return Objects.hash(name, age, score);
   }
}

3.1中間操作:

3.1.1篩選與切片

filter--過(guò)濾临谱,接收l(shuí)ambada,從流中過(guò)濾掉不滿(mǎn)足給定條件的元素奴璃。源碼如下:

Stream<T> filter(Predicate<? super T> predicate);

filter的參數(shù)是一個(gè)Predicate的斷言類(lèi)型悉默,給定一個(gè)參數(shù),返回一個(gè)boolean值苟穆,判斷是否滿(mǎn)足要求抄课。

栗子:

@Test
public void test01(){
   students.stream().filter(sudent ->sudent.getAge() > 16).forEach(System.out::println);
}

limit--截?cái)嗔鳎瞧湓夭怀^(guò)給定數(shù)量雳旅。

@Test
public void test03(){
   //limit符合短路規(guī)則跟磨,找到兩條就不在向下遍歷
   students.stream().filter(sudent ->sudent.getAge() > 16).limit(2).forEach(System.out::println);
}

skip(n)--跳過(guò)元素,返回一個(gè)跳過(guò)了前n個(gè)元素的流攒盈,若流中的元素個(gè)數(shù)不足n個(gè)抵拘,則返回一個(gè)空流。

@Test
public void test04(){
   students.stream().filter(sudent ->sudent.getAge() > 16).skip(2).forEach(System.out::println);
}

distinct--篩選型豁,通過(guò)流所生成元素的hashCode()和equals()去除重復(fù)元素僵蛛。

@Test
public void test05(){
   //distinct去重,使用distinct去重必須重寫(xiě)hashCode和equals方法
   students.stream().filter(sudent ->sudent.getScore() < 60).distinct().forEach(System.out::println);
}

3.1.2映射

map--接收l(shuí)ambada迎变,將元素轉(zhuǎn)換成其他形式或提取信息充尉。接收一個(gè)函數(shù)作為參數(shù),該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上衣形,并將其映射成一個(gè)新的元素驼侠,最后返回新流。源碼如下:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

map方法的參數(shù)是一個(gè)Function函數(shù)式接口類(lèi)型來(lái)表示我們要做的操作谆吴,給定一個(gè)參數(shù)倒源,返回結(jié)果,F(xiàn)unction的apply方法的參數(shù)和返回值類(lèi)型都是泛型類(lèi)型,map的返回值是一個(gè)帶泛型的Stream類(lèi)型纪铺,即返回一個(gè)新的流相速。

栗子:

@Test
public void test06(){
   students.stream().map(Student::getName).forEach(System.out::println);
}

flatMap--扁平映射碟渺,接收一個(gè)函數(shù)作為參數(shù)鲜锚,將流中的每個(gè)元素都轉(zhuǎn)換成另一個(gè)流突诬,然后把所有流連接成一個(gè)流。源碼如下:

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

flatMap的參數(shù)也是一個(gè)Function類(lèi)型芜繁,不過(guò)和map不同的是旺隙,flatMap的Function接口的第二個(gè)泛型類(lèi)型不是R,而是Stream<? extends R>或它的子類(lèi)骏令。
在介紹flatMap之前蔬捷,我們先舉一個(gè)使用map處理的不太友好的栗子:

private static Stream<Character> filterCharacter(String str){
   List<Character> list = new ArrayList<>();
   for (char c : str.toCharArray()) {
       list.add(c);
   }
   return list.stream();
}

@Test
public void test07(){
   List<String> list = Arrays.asList("hello","world");
   Stream<Stream<Character>> streamStream = list.stream().map(StreamTest04::filterCharacter);
   streamStream.forEach((stream) -> stream.forEach(System.out::println));
}

以上,我們的filterCharacter方法返回的是一個(gè)流榔袋,而map也返回一個(gè)新流周拐,所以接收的類(lèi)型就是 Stream<Stream<Character>>,流中包含流凰兑,最外層的這個(gè)流包含兩個(gè)流元素妥粟,它們分別是流1[h,e,l,l,o]和流2[w,o,r,l,d],這對(duì)我們的遍歷不太方便,需要兩次遍歷才能遍歷到具體的元素吏够。

流這塊兒不太好調(diào)試勾给,所以我把代碼稍微修改一下,驗(yàn)證上面的結(jié)論:

@Test
public void test07(){
   List<String> list = Arrays.asList("hello","world");
   Stream<Stream<Character>> streamStream = list.stream().map(StreamTest04::filterCharacter);
   streamStream.forEach(stream ->{
       System.out.println(stream);
       stream.forEach(System.out::println);
       System.out.println("---------------------------------------------");
   } );
}
執(zhí)行結(jié)果

使用flapMap

@Test
public void test08(){
   List<String> list = Arrays.asList("hello","world");
   final Stream<Character> characterStream = list.stream().flatMap(StreamTest04::filterCharacter);
   characterStream.forEach(System.out::println);
}

flapMap將調(diào)用filterCharacter方法后生成的兩個(gè)映射流給它打碎了锅知,把每個(gè)映射流中以前的元素整合到一個(gè)新流中播急,這個(gè)新流為[h,e,l,l,o,w,o,r,l,d],并將新流返回。

以上的兩個(gè)映射方法返回的都是帶泛型的Stream類(lèi)型售睹,而泛型只能用包裝類(lèi)桩警,而不能用基本的數(shù)值類(lèi)型,比如int,long,double等昌妹,而只能用他們對(duì)應(yīng)的包裝類(lèi)型生真,這樣自動(dòng)地裝箱拆箱也會(huì)損耗一定的性能,所以Stream接口還提供了幾個(gè)可以使用基本數(shù)值類(lèi)型int捺宗,long,double的映射方法柱蟀,他們分別是mapToInt,mapToLong,mapToDouble。源碼分別如下:

IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

以上三個(gè)方法的返回類(lèi)型分別為IntStream蚜厉,LongStream长已,DoubleStream。這三個(gè)接口就是為了方便我們使用基本數(shù)值類(lèi)型而設(shè)計(jì)的Stream類(lèi)型昼牛,里面也有一些使用的方法可以直接使用术瓮,這里不做介紹,感興趣的朋友可以看看源碼贰健。

舉個(gè)栗子:

Stream<Integer> stream = Stream.iterate(0, integer -> integer + 2).limit(10);
OptionalInt min = stream.filter(integer -> integer > 2).mapToInt(value -> value * 2).skip(2)
      .limit(2).min();
min.ifPresent(System.out::println);

3.1.3排序

sorted()--自然排序

@Test
public void test09(){
   List<String> list = Arrays.asList("a","b","c","d","e");
   list.stream().sorted().forEach(System.out::println);
}

sorted(Comparator comparator)--定制排序

@Test
public void test10(){
   students.stream().sorted((student1,student2) -> {
       if(student1.getAge().equals(student2.getAge())){
           return student1.getName().compareTo(student2.getName());
       }else {
           return student1.getAge().compareTo(student2.getAge());
       }
   }).forEach(System.out::println);
}

3.2終止操作

3.2.1查找與匹配
allMatch--檢查是否匹配所有元素
anyMatch--檢查是否至少一個(gè)元素
noneMatch--檢查是否沒(méi)有匹配所有元素
findFirst--返回第一個(gè)元素
findAny--返回當(dāng)前流中任意元素
count--返回流中元素的總個(gè)數(shù)
max--返回流中最大值
min--返回流中最小值

@Test
public void test11(){
   //判斷是否所有的學(xué)生都小于30歲
   final boolean b = students.stream().allMatch(student -> student.getAge() < 30);
   System.out.println(b);
   //判斷是否有學(xué)生小于15歲
   System.out.println(students.stream().anyMatch(student -> student.getAge() < 15));
   //判斷是否是否沒(méi)有學(xué)生的年齡小于10
   System.out.println(students.stream().noneMatch(student -> student.getAge() < 10));
   //獲取所有學(xué)生的成績(jī)由大到小排序后的第一個(gè)學(xué)生信息
   Optional<Student> first = students.stream().sorted((s1, s2)
           -> Double.compare(s2.getScore(), s1.getScore())).findFirst();
   first.ifPresent(System.out::println);
   //通過(guò)并行流獲取成績(jī)小于60的任意一個(gè)學(xué)生的信息
   Optional<Student> any = students.parallelStream().filter(student -> student.getScore() < 60).findAny();
   any.ifPresent(System.out::println);
   //獲取所有學(xué)生的人數(shù)
   System.out.println(students.stream().count());
   //獲取成績(jī)最高的學(xué)生的信息
   Optional<Student> max = students.stream().max(Comparator.comparingDouble(Student::getScore));
   max.ifPresent(System.out::println);
   //獲取成績(jī)最低的學(xué)生的成績(jī)
   Optional<Double> min = students.stream().map(Student::getScore).min(Double::compareTo);
   min.ifPresent(System.out::println);
}

3.2.2歸約
歸約(reduce)可以將流中元素通過(guò)一個(gè)計(jì)算函數(shù)計(jì)算后胞四,得到一個(gè)值
Stream源碼中有兩個(gè)reduce方法,一個(gè)是不可能為空伶椿,返回泛型類(lèi)型T辜伟;一個(gè)是結(jié)果可能為空氓侧,返回Optional<T>,避免空指針导狡。源碼如下:

1.結(jié)果不可能為空的情況:

T reduce(T identity, BinaryOperator<T> accumulator);

identity指從哪開(kāi)始計(jì)算约巷,accumulator累加器是一個(gè)BinaryOperator的函數(shù)式接口,源碼如下:

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {}

BinaryOperator繼承于BiFunction旱捧,只是它的兩個(gè)參數(shù)類(lèi)型和返回值類(lèi)型都是T類(lèi)型独郎。

栗子:

@Test
public void test13(){
   List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
   //結(jié)果不可能為空,所以不用封裝到Optional中
   Integer sum = list.stream().reduce(0, Integer::sum);
   System.out.println(sum);
}

Integer::sum通過(guò)靜態(tài)方法引用調(diào)用Integer類(lèi)中的sum方法求和枚赡,sum方法傳入兩個(gè)int值氓癌,返回求和結(jié)果,還是int值贫橙。源碼如下:

public static int sum(int a, int b) {
   return a + b;
}

2.結(jié)果可能為空的情況:

Optional<T> reduce(BinaryOperator<T> accumulator);

栗子:

@Test
public void test12(){
   //結(jié)果可能為空顽铸,所以封裝到Optional中
   Optional<Double> optional = students.stream().map(Student::getScore).reduce(Double::sum);
   optional.ifPresent(System.out::println);
}

3.2.3收集

collect--將流轉(zhuǎn)換為其他形式,接收一個(gè)Collector接口的實(shí)現(xiàn)料皇,用于給Stream中元素做匯總的方法谓松,collect源碼如下:

<R, A> R collect(Collector<? super T, A, R> collector);

Collector接口中提供了一些具體操作的函數(shù)式接口方法及其他方法:

public interface Collector<T, A, R> {
   Supplier<A> supplier();
   BiConsumer<A, T> accumulator();
   BinaryOperator<A> combiner();
   Function<A, R> finisher();
   Set<Characteristics> characteristics();
}

不過(guò)我們一般不直接對(duì)Collector接口進(jìn)行操作,我們常用的是Collectors工具類(lèi)践剂,Collectors中定義了一個(gè)內(nèi)部類(lèi)鬼譬,這個(gè)內(nèi)部類(lèi)實(shí)現(xiàn)了Collector接口:

static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
   private final Supplier<A> supplier;
   private final BiConsumer<A, T> accumulator;
   private final BinaryOperator<A> combiner;
   private final Function<A, R> finisher;
   private final Set<Characteristics> characteristics;
    ...
}

這個(gè)工具類(lèi)中提供了一些靜態(tài)方法供我們使用,比如我們常用的toList方法逊脯,toSet方法优质,將流轉(zhuǎn)換為集合。

栗子:

@Test
public void test14(){
   //將所有學(xué)生的姓名提取出來(lái)收集到一個(gè)集合中
   List<String> list = students.stream().map(Student::getName).collect(Collectors.toList());
   list.forEach(System.out::println);
}

如果Collectors中沒(méi)有我們想要將流轉(zhuǎn)換成的類(lèi)型军洼,還可以使用Collectors提供的toCollection方法:

@Test
public void test15(){
   //將所有學(xué)生的姓名提取出來(lái)收集到一個(gè)集合中
   Collection<String> collect = students.stream().map(Student::getName).collect(Collectors.toCollection(HashSet::new));  
   collect.forEach(System.out::println);
}

collect方法還有一個(gè)重載方法巩螃,只是這個(gè)重載方法用的不是很多,這里也分析一下哈匕争,感興趣的朋友可以看下哈避乏,不敢興趣,跳過(guò)即可甘桑。

<R> R collect(Supplier<R> supplier,
             BiConsumer<R, ? super T> accumulator,
             BiConsumer<R, R> combiner);

supplier拍皮,提供者函數(shù),是Supplier函數(shù)式接口類(lèi)型跑杭,不傳入任何參數(shù)铆帽,有一個(gè)返回值,這里是指新結(jié)果的容器德谅,也就是collect方法要返回的結(jié)果爹橱。

accumulator累加器函數(shù),是BiConsumer消費(fèi)式函數(shù)式接口類(lèi)型窄做,傳入兩個(gè)參數(shù)愧驱,不返回任何值慰技。這里用于對(duì)流中的每一個(gè)元素進(jìn)行遍歷,將遍歷到的每個(gè)元素合并到結(jié)果中冯键。

combiner,組合器函數(shù)庸汗,也是BiConsumer類(lèi)型惫确,與上面所不同的是,這個(gè)BiConsumer的兩個(gè)參數(shù)都是R類(lèi)型蚯舱,必須與累加器函數(shù)兼容改化,也就是要與累加器函數(shù)中第一個(gè)參數(shù)代表的的容器類(lèi)型相同。

舉個(gè)栗子:

Stream<String> stream = Stream.of("beijing", "shanghai", "tianjin");
List<String> list = stream.collect(() -> new ArrayList<>(),(theList,item) -> theList.add(item),
       (theList1,theList2) -> theList1.addAll(theList2));
list.forEach(System.out::println);

上述代碼等價(jià)于下面使用方法引用代碼:

Stream<String> stream = Stream.of("beijing", "shanghai", "tianjin");
List<String> list = stream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
list.forEach(System.out::println);

上面栗子我們是要把一個(gè)stream流轉(zhuǎn)化為一個(gè)集合容器枉昏,并將stream流流中原來(lái)的三個(gè)元素陈肛,biejing,shanghai,tianjin,轉(zhuǎn)變?yōu)樾碌募先萜鞯脑亍?/p>

轉(zhuǎn)換過(guò)程(collect三個(gè)參數(shù)具體運(yùn)作流程):

首先我們先需要用supplier提供者函數(shù)創(chuàng)建一個(gè)容器兄裂,來(lái)裝流中的三個(gè)元素句旱,最后返回的結(jié)果也是這個(gè)容器。

接下來(lái)我們就要用accumulator這個(gè)累加器函數(shù)來(lái)來(lái)遍歷stream流中的每個(gè)元素晰奖,并把每次遍歷到的元素都添加到一個(gè)集合中谈撒。這個(gè)集合相當(dāng)于一個(gè)中間集合,并不是我們上面用supplier提供者函數(shù)創(chuàng)建的要作為結(jié)果返回的集合容器匾南。

最后我們用combiner組合器函數(shù)把上面中間集合里的元素一次性地都添加到我們要返回結(jié)果的集合容器中啃匿,也就是將theList中的元素都添加到theList1中,theList1就相當(dāng)于supplier提供者函數(shù)創(chuàng)建的要作為結(jié)果返回的集合容器蛆楞,theList2就相當(dāng)于上面的theList.

下面我們來(lái)驗(yàn)證一下以上第二步的分析是否和執(zhí)行結(jié)果相同:

public class StreamTest03 {

   public static void main(String[] args) {
       Stream<String> stream = Stream.of("beijing", "shanghei", "tianjin");
       List<String> list = stream.collect(() -> new ArrayList<>(),(theList,item) -> {
                   theList.add(item);
                   System.out.println(theList);
               },
               (theList1,theList2) -> theList1.addAll(theList2));
   }
}
執(zhí)行結(jié)果

我們的流中有三個(gè)元素溯乒,所以在第二步中經(jīng)過(guò)了三次遍歷,每次遍歷到一個(gè)元素豹爹,并將每次遍歷到的元素添加到theList集合中裆悄,三次遍歷后theList集合的元素為:[beijing, shanghei, tianjin]。

怎么樣臂聋?是不是不太好理解灯帮,這個(gè)重載方法確實(shí)比較難容易理解,不過(guò)多琢磨琢磨逻住,結(jié)合著源碼和示例代碼練習(xí)一下還是會(huì)理解的钟哥,我學(xué)習(xí)這個(gè)方法也琢磨了挺長(zhǎng)時(shí)間。

再舉一例吧瞎访,jdk官方文檔的一個(gè)栗子:

String concat = stream.collect(StringBuilder::new, StringBuilder::append,
       StringBuilder::append)
       .toString();

我們稍微修改一下腻贰,把方法引用改成用lambada表達(dá)式更容易理解。

Stream<String> stream = Stream.of("beijing", "shanghai", "tianjin");
StringBuilder collect = stream.collect(() -> new StringBuilder(), (stringBuilder, item) -> stringBuilder.append(item),
       (stringBuilder1, stringBuild2) -> stringBuilder1.append(stringBuild2));
System.out.println(collect.toString());

執(zhí)行結(jié)果是一樣的:

執(zhí)行結(jié)果

再看一下Collectors.toList()將流轉(zhuǎn)換為list集合的底層實(shí)現(xiàn)扒秸,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);
}

看到這里我們應(yīng)該恍然大悟了吧播演,原來(lái)Collectors.toList()方法用的就是我們上面分析的帶三個(gè)參數(shù)的collect的重載方法實(shí)現(xiàn)的冀瓦,二者的本質(zhì)是一樣的。

除了以上的toList,toConllection,toSet方法以外写烤,Collectors工具類(lèi)還為我們提供了一些其他的實(shí)用的方法翼闽,比如求總數(shù),平均數(shù)洲炊,最大值感局,最小值,分組暂衡,分區(qū)等询微,下面就介紹一些我們比較常用的方法。

@Test
public void test16(){
   //獲取學(xué)生人數(shù)
   Long count = students.stream().collect(Collectors.counting());
   System.out.println(count);

   //求學(xué)生成績(jī)的平均分
   Double averageScore = students.stream().collect(Collectors.averagingDouble(Student::getScore));
   System.out.println(averageScore);

   //求所有學(xué)生總成績(jī)
   double sumScore = students.stream().collect(Collectors.summingDouble(Student::getScore));
   System.out.println(sumScore);

   //獲取成績(jī)最高的學(xué)生信息
   Optional<Student> max = students.stream().collect(Collectors.maxBy((s1, s2)
           -> Double.compare(s1.getScore(), s2.getScore())));
   max.ifPresent(System.out::println);

   //獲取成績(jī)最底的學(xué)生的成績(jī)
   Optional<Double> min = students.stream().map(Student::getScore).collect(Collectors.minBy(Double::compareTo));
   min.ifPresent(System.out::println);

   //按照l(shuí)evel將所有學(xué)生分組
   Map<String, List<Student>> listMap = students.stream().collect(Collectors.groupingBy(Student::getLevel));
   listMap.forEach((level, students) -> {
       System.out.println(level+":"+students);
   });
   System.out.println();

   Map<String, Long> collect = students.stream().collect(Collectors.groupingBy(Student::getLevel, Collectors.counting()));
   collect.forEach((level,counts) -> System.out.println(level + ":" + counts));

   System.out.println();
   //分區(qū)狂巢,成績(jī)大于等于60為及格區(qū)撑毛,小于60為不及格區(qū)
   Map<Boolean, List<Student>> booleanListMap = students.stream().collect(Collectors.partitioningBy(student
           -> student.getScore() >= 60));
   booleanListMap.forEach((bool, students) -> System.out.println(bool + ":" + students));
}

這樣使用Stream流是不是有點(diǎn)像寫(xiě)sql語(yǔ)句,其實(shí)jdk8中的stream流挺類(lèi)似于sql語(yǔ)句的唧领,都是一種描述性語(yǔ)言藻雌,讓開(kāi)發(fā)者只需要寫(xiě)出想要的結(jié)果對(duì)應(yīng)的語(yǔ)句,而不用關(guān)心這些語(yǔ)句的底層是怎么實(shí)現(xiàn)和執(zhí)行的斩个。

同時(shí)Collectors還有一類(lèi)統(tǒng)計(jì)方法蹦疑,用于統(tǒng)計(jì)個(gè)數(shù),總數(shù)萨驶,最大值歉摧,最小值以及求平均數(shù)等,這類(lèi)方法有summarizingInt腔呜,summarizingLong叁温,summarizingDouble,分別對(duì)應(yīng)于數(shù)值類(lèi)型的整型核畴,長(zhǎng)整型膝但,double類(lèi)型。以summarizingDouble為例介紹下如何使用谤草。

@Test
public void test17(){
  //統(tǒng)計(jì)
   DoubleSummaryStatistics summaryStatistics = students.stream()
           .collect(Collectors.summarizingDouble(Student::getScore));
   System.out.println(summaryStatistics.getCount());
   System.out.println(summaryStatistics.getSum());
   System.out.println(summaryStatistics.getMax());
   System.out.println(summaryStatistics.getMin());
   System.out.println(summaryStatistics.getAverage());
}

隨后一個(gè)方法跟束,連接方法:

@Test
public void test18(){
   //連接
   String collect = students.stream().map(Student::getName).collect(Collectors.joining(","));
   System.out.println(collect);
}

4.注意
提一點(diǎn)使用流的注意事項(xiàng),就是我們?cè)谑褂昧鲿r(shí)要注意流是不能被重復(fù)使用或消費(fèi)的丑孩。不然會(huì)報(bào)下面的異常冀宴。

stream has already been operated upon or closed

栗子:

public class StreamTest06 {

   public static void main(String[] args) {
       Stream<Integer> stream = Stream.iterate(0, integer -> integer + 2).limit(10);
       System.out.println(stream);
       System.out.println(stream.filter(integer -> integer > 2));
       //stream has already been operated upon or closed,因?yàn)楹蜕厦媸褂玫氖峭粋€(gè)流對(duì)象,而一個(gè)流的對(duì)象不能被重復(fù)使用
       System.out.println(stream.distinct());//和上面用的同一個(gè)stream
   }
}
執(zhí)行結(jié)果

修改一下使之用不同的stream:

public class StreamTest06 {

   public static void main(String[] args) {
       Stream<Integer> stream = Stream.iterate(0, integer -> integer + 2).limit(10);
       System.out.println(stream);
       Stream<Integer> integerStream = stream.filter(integer -> integer > 2);
       System.out.println(integerStream);
       System.out.println(integerStream.distinct());
   }
}

5.總結(jié)

終于寫(xiě)完了温学,是不是很啰嗦略贮,太長(zhǎng)了,為了保持內(nèi)容的連續(xù)性就沒(méi)有拆分成幾篇文章,腦瓜疼逃延,難免有介紹的不到位的地方览妖,也可能有錯(cuò)別字,雖然我已經(jīng)檢查了好幾遍揽祥,大家就湊活看吧讽膏,能對(duì)大家的學(xué)習(xí)起到點(diǎn)幫助作用就可以了,同時(shí)我自己也可以回過(guò)頭來(lái)復(fù)習(xí)已經(jīng)忘記的知識(shí)拄丰。如果大家發(fā)現(xiàn)有不對(duì)的地方府树,歡迎留言指正,錯(cuò)別字也可以愈案。輸出不易挺尾,大家給點(diǎn)個(gè)贊也行(皮一下哈)鹅搪,同時(shí)也歡迎大家關(guān)注微信公眾號(hào)站绪,后期會(huì)有更多的輸出,共同學(xué)習(xí)丽柿,不斷進(jìn)步恢准,持續(xù)輸出。

點(diǎn)點(diǎn)關(guān)注甫题,點(diǎn)關(guān)注馁筐,不迷路
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市坠非,隨后出現(xiàn)的幾起案子敏沉,更是在濱河造成了極大的恐慌,老刑警劉巖炎码,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盟迟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡潦闲,警方通過(guò)查閱死者的電腦和手機(jī)攒菠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)歉闰,“玉大人辖众,你說(shuō)我怎么就攤上這事『途矗” “怎么了凹炸?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)昼弟。 經(jīng)常有香客問(wèn)我还惠,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任蚕键,我火速辦了婚禮救欧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锣光。我一直安慰自己笆怠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布誊爹。 她就那樣靜靜地躺著蹬刷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪频丘。 梳的紋絲不亂的頭發(fā)上办成,一...
    開(kāi)封第一講書(shū)人閱讀 51,541評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音搂漠,去河邊找鬼迂卢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛桐汤,可吹牛的內(nèi)容都是我干的而克。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼怔毛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼员萍!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起拣度,我...
    開(kāi)封第一講書(shū)人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤碎绎,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后抗果,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體筋帖,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年窖张,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了幕随。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宿接,死狀恐怖赘淮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情睦霎,我是刑警寧澤梢卸,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站副女,受9級(jí)特大地震影響蛤高,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一戴陡、第九天 我趴在偏房一處隱蔽的房頂上張望塞绿。 院中可真熱鬧,春花似錦恤批、人聲如沸异吻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)诀浪。三九已至,卻和暖如春延都,著一層夾襖步出監(jiān)牢的瞬間雷猪,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工晰房, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留求摇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓嫉你,卻偏偏與公主長(zhǎng)得像月帝,于是被迫代替她去往敵國(guó)和親躏惋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子幽污,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容