Java8新特性學習(三)- Stream類

Java8新特性學習(三)- Stream類

背景及介紹

這裏提到Java8的Stream類並不像Java以前版本的InputStream和OutputStream,他們是幾乎不搭邊的兩個類。Stream類常跟集合處理一起使用,算是集合的一個增強。Stream類比前面提到的Optional類更加通用,原因在於Stream類提供更豐富的API,滿足了程序中通用的集合處理方法,且使用lambda方便,較Java7減少較多的代碼。

舉例說明

使用Stream和lambda編碼,可以提高代碼效率,對於過濾取值這種常規操作,相比起java7來實現高效簡單。下面舉例說明,假設我們有一組求職者的簡歷集合,我們需要從中過濾具有3年以上工作經驗,且有高併發技能的Java開發工程師的求職者,得到他們的手機號,進行面試邀約。

    public class Resume {
        private String name;
        private Integer workingAge;
        private List<String> skillList;
        private String job;
        private String phone;
    }
		//java7
        List<String> phoneList = new ArrayList<>();
        for (Resume resume : resumeList) {
            if (resume.getWorkingAge() < 3) {
                continue;
            }
            if (!resume.getSkillList().contains("高併發")) {
                continue;
            }
            if (!"Java".equals(resume.getJob())) {
                continue;
            }
            phoneList.add(resume.getPhone());
        }
		//java8
        List<String> phoneList = resumeList.stream()
                .filter(resume -> resume.getWorkingAge() >= 3)
                .filter(resume -> resume.getSkillList().contains("高併發"))
                .filter(resume -> "Java".equals(resume.getJob()))
                .map(Resume::getPhone).collect(Collectors.toList());

重要方法介紹

java.util.Collection#stream#parallelStream

集合類對象可以直接通過stream()/paralleStream()方法創建串行流和並行流。如

        List<String> phoneList = resumeList.stream()
                .map(Resume::getPhone).collect(Collectors.toList());

of\generate

of()用來顯式創建串行的Stream對象,有兩個重載方法,一個是元素個數不限,另一個只能傳一個值。基礎數據類型的流,可以擴展使用IntStream、LongStream、DoubleStream.

特別說明:流只能使用一次,不然會拋異常java.lang.IllegalStateException: stream has already been operated upon or closed.

		//of()
        Stream<Integer> integerStream = Stream.of(1,2,3,4,5,6);
        integerStream.filter(i -> i > 4).forEach(System.out::println);

這裏創建的是串行的運行方式,如果想要創建並行的,可以通過Colletion#parallelStream()方法創建。of()方法暫時沒有提供這個特性。

generate()方法可以自己生成控制Stream,這個方法適合用來創建常量的Stream或者隨機元素的Stream。generate()是無限生成流的,需要配合limit()來使用.下面舉例在1000個數字中隨機抽取5個幸運中獎號碼.

		//generate()
Stream.generate(() -> new Random().nextInt(1000)).limit(5).forEach(System.out::println);

filter

filter會對Stream中的所有元素進行過濾,會創建一個新的Stream,其元素均符合過濾條件。如文章開頭舉例的簡歷過濾:

		//java8
        List<String> phoneList = resumeList.stream()
                .filter(resume -> resume.getWorkingAge() >= 3)
                .filter(resume -> resume.getSkillList().contains("高併發"))
                .filter(resume -> "Java".equals(resume.getJob()))
                .map(Resume::getPhone).collect(Collectors.toList());

map/flatMap

map和flatMap都可以被用在一個Stream對象上,且都能返回Stream.不同之處在於map操作會對每一個輸入返回一個結果,然而flatMap操作會返回任意個(0個/1個/多個)值.這取決於在每個方法上的操作。

map操作接受一個Function,會將傳入的每個值都調用改Function,然後得到結果後返回。而flatMap操作會對每個參數產生任意數量結果。在Java編程中,返回任意結果可能會在處理時比較麻煩,需要考慮更多的情況,因爲返回值可能是一個值,Array或List。我們可以把flatMap操作可以看成是map操作 + flat操作,首先應用Function得到結果,然後將結果flat出來。而map操作相比flatMap操作則可以視爲沒有最後的flat操作。下面舉例:

    @Test
    public void testFlatMap() {
        List<Integer> intList1 = new ArrayList<>();
        intList1.add(1);
        intList1.add(2);
        List<Integer> intList2 = new ArrayList<>();
        intList1.add(3);
        intList1.add(4);

        List<Integer> list = Stream.of(intList1, intList2).flatMap(List::stream).collect(Collectors.toList());
        System.out.println(list);
        //[1, 2, 3, 4]
    }
    

再舉例,現在有幾個單詞,想過濾單詞中不重複的字母。map操作無法得到,而flatMap可以得到。

        //map
        List<Stream<String>> sMap = Arrays.stream(words).map(word -> word.split(""))
                .map(Arrays::stream).distinct()
                .collect(Collectors.toList());

        //flatMap
        List<String> sFlatMap = Arrays.stream(words).map(word -> word.split(""))
                .flatMap(Arrays::stream).distinct()
                .collect(Collectors.toList());

        System.out.println(sFlatMap);
        //[J, A, V, G, O]

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

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

collect

collect方法用於將Stream流中的對象轉化還原爲流的結果。如

    List<String> asList = stringStream.collect(Collectors.toList());
    
    Map<String, List<Person>> peopleByCity   = personStream.collect(Collectors.groupingBy(Person::getCity));
    
    Map<String, Map<String, List<Person>>> peopleByStateAndCity    = personStream.collect(Collectors.groupingBy(Person::getState,                                                       Collectors.groupingBy(Person::getCity)));

anyMatch\allMatch

anyMatch:檢測是否流中是否有任一元素滿足提供Predicate.

allMatch:檢測是否流中是否所有元素滿足提供Predicate.

        boolean anyMatch = Stream.of(1, 2, 3, 4, 5, 6).anyMatch(i -> i > 6);
        System.out.println(anyMatch);//false
        boolean allMatch = Stream.of(1, 2, 3, 4, 5, 6).allMatch(i -> i < 20);
        System.out.println(allMatch);//true

min\max

根據參數Comparator返回流中最大\小的元素,返回類型是Optional。更多Optional信息,請查看
Java8新特性學習(二)- Optional類

        Optional<Integer> optionalMin = Stream.of(1, 2, 3, 4, 5, 6).min((a, b) -> a > b ? 1 : -1);
        System.out.println(optionalMin.orElse(null));//1

        Optional<Integer> optionalMax = Stream.of(1, 2, 3, 4, 5, 6).max(Integer::compareTo);
        System.out.println(optionalMax.orElse(null));//6

skip\limit

skip:返回除去前n個元素之外剩餘元素組成的流。如果全部被拋棄,則返回一個空流。

limit:返回最大元素個數不超過n的流

        Stream.of(1,2,3,4,5,6).skip(20l).forEach(System.out::print);
        Stream.of(1,2,3,4,5,6).limit(3l).forEach(System.out::print);

reduce

reduce是關聯累積函數對流的元素進行縮減,並返回縮減之後的Optional類型的結果。

reduce()提供了3種方式使用

1、Optional<T> reduce(BinaryOperator<T> accumulator);//返回值是Optional

2、T reduce(T identity, BinaryOperator<T> accumulator);//返回值是T,identity一樣的類型

3<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);
                 //返回值是U,identity一樣的類型,accumulator累加器,combiner:結合器

舉例使用

	System.out.println(Stream.of(1,2,3,4,5,6).reduce( (a, b) -> a+b).orElse(0));
	System.out.println(Stream.of(1,2,3,4,5,6).reduce(0, (a, b) -> a+b));

這裏會對Stream中的元素遍歷每一個,進行BinaryOperator的apply操作,並最終返回一個類似合併各元素之後的結果。而我們常用的sum,max,min等操作都是一種特殊的reduce操作。可以看IntStream中min()函數的實現:

    public final OptionalInt min() {
        return reduce(Math::min);
    }

總結

  • Stream不是一種數據結構
  • Stream並不會自己存儲數據,而是利用原數據進行操作
  • Stream只能使用一次,使用多次會拋出異常
  • 具有串行和並行能力,簡單高效就能寫出高併發代碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章