Java--流 Stream

1.概论

  • 流提供了一种让我们可以在比集合更高的概念级别上指定计算的数据视图。通过使用流,我们可以说明想要完成什么任务,而不是说明如何去实现它。

2.流与集合的区别

  • 流并不存储其元素,这些元素可能存储在底层的集合中,或者按需生成。
  • 流的操作不会修改其数据源。
  • 流的操作是尽可能惰性执行的。这意味着直至需要其结果时,操作才会执行。

3.流的创建

  1. 使用Stream.empty()创建不包含任何元素的流。
    Stream<String> stream = Stream.empty();

  2. 创建无限流。一个是使用Stream.generate(Supplier<T>),传入一个Supplier<T>接口对象。
    Stream<Double> stream=Stream.generate(Math::random);
    或者通过下面的方法获得一个常量值的流:
    Stream<String> stream = Stream.generate(() -> "Echo");

  3. 另一个获取无限流的方法是Stream.iterate( seed, UnaryOperator);该方法会接受一个“种子”,以及一个函数(UnaryOperator<T>),并且会反复地将该函数应用到之前的结果上。
    Stream<BigInteger> integers =Stream.iterate(BigInteger.ZERO,n->n.add(BigInteger.ONE));
    该流中第一个元素是种子BigInteger.ZERO,第二个元素是f(seed),即,1,下一个元素是f(f(seed)),即2.后续以此类推。

  4. Collection接口的stream()将任何集合转换为一个流。
    List<String> words = ...; words.stream()...

  5. 使用Stream类的静态方法,传入一个数组生成流
    Stream<String> words = Stream.of("apple","banana"});

  6. 使用Arrays.stream(array,from,to) 可以从数组中位于from(包括)和to(不包括)的元素中创建一个流。
    String[] array = new String[]{"apple","banana","orange"};
    Stream<String> stream = Arrays.stream(array,1,2);

  7. 静态的Files.lines(Path)方法会返回一个包含了文件中所有行的Stream:
    Stream<String> lines = Files.lines(path);

4.流的转换

  1. filter(Predicate<T>)
    对流中的元素进行过滤,引元是Predicate<T>,即从T到boolean的函数。
    List<String> wordList=...;
    Stream<String> longWords = wordList.stream().filter(w -> w.length() > 12);
  2. map(Function<? super T, ? extends R> mapper)
    对流中的元素应用提供的函数,引元是Function<? super T, ? extends R> mapper
    Stream<String> firstLetters = words.stream().map(s -> s.substring(0,1));
  3. limit(long maxSize):返回一个新的流,其中包含当前流中最初的maxSize个元素
    Stream<Double> words = Stream.generate(Math::random).limit(100);
  4. skip(long n):产生一个流,它的元素是当前流中除了前n个元素之外的所有元素。
    Stream<String> words = Stream.of("apple","banana","orange").skip(1);
  5. concat(Stream<? extends T> a, Stream<? extends T> b):产生一个流,它的元素是a的元素后面跟着b的元素。
    Stream<String> combined = Stream.concat(Stream.of("apple","banana"),Stream.of("pear"));
    ///combined中的元素 ["apple","banana","pear"]
    需要注意的是,第一个流不应该是无限的,否则第二个流永远不会被处理。
  6. distinct():会返回一个根据原来流中的元素剔除重复元素后产生的流。元素的顺序不变。
    Stream<String> uniqueWords = Stream.of("apple","apple","banana","banana","orange");
    ///uniqueWords 中的元素["apple","banana","pear"]
  7. peek(Consumer<? super T> action):产生一个流,它与当前流中的元素相同,在获取其中每个元素时,会将其传递给action。

5.Optional类型

  1. 概论:Optional<T>对象是一种包装器对象,要么包装了类型T的对象,要么没有包装任何对象。对于第一种情况,我们称这种值为存在的。Optional<T>类型被当作一种更安全的方式,用来替代类型T的引用,这种引用要么引用某个对象,要么为null。
  2. Optional的使用
    • 如何在不存在任何值的情况下产生相应的替代物
    • 当Optional的值不存在时,希望使用某种默认值替代。
      String result = optionalString.orElse("");
      //result的值为optionalString包装的字符串,如果为null,则为空字符串
    • 或者调用代码计算默认值:
      String result = optionalString.orElseGet(() -> "");
      //如果optionalString包装的字符串为null,则调用传入的函数生成默认值。
    • 或者可以在没有任何值时抛出异常
      String result = optionalString.orElseThrow(IllegalStateException::new);
      //提供一个生成异常对象的函数。
    • 只有在其存在的情况下才消费该值。
    • ifPresent方法会接受一个函数,如果该可选值存在,那么它会被传递给该函数。否则,不会发生任何事情。
      optionalValue.ifPresent(v -> result.add(v) );
    • 当调用ifPresent时,从该函数不会返回任何值。如果想要处理函数的结果,应该使用map:
      Optional<Boolean> added = optionalValue.map(results::add);
      此时的added具有三种值之一:在optionalValue存在的情况下包装在Optional中的true或false,以及在optionalValue不存在的情况下的空Optional。
  3. 创建Optional值
    • Optional.of(T value)
    • Optional.ofNullable(T value)

    产生一个具有给定值的Optional。如果value为null,那么第一个方法会抛出NullPointerException对象,而第二个方法会产生一个空Optional。

    • Optional.empty() :产生一个空Optional对象。
  4. 用flatMap来构建Optional值的函数
    假设你有一个可以产生Optional<T>对象的方法f,并且目标类型T具有一个可以产生Optional<U>对象的方法g。如果他们都是普通方法,那么你可以调用s.f().g()来将他们组合起来。但是这种组合没法工作,因为s.f()的类型为Optional<T>,而不是T。因此,需要调用:
    Optional<U> result = s.f().flatMap(T::g);
    如果s.f()的值存在,那么g就可以应用到它上面。否则,就会返回一个空Optional<U>。
    很明显,如果有更多的可以产生Optional值的方法或Lambda表达式,那么就可以重复此过程。你可以直接将对flat Map的调用链接起来,从而构建由这些步骤构成的管道,只有所有不走都成功时,该管道才会成功。

6.收集结果

  • iterator(),产生可以用来访问元素的迭代器。
  • forEach(Consumer<? super T> action),将某个函数应用于每个元素。

stream.iterator()
stream.forEach(System.out::println);

  • toArray():返回一个Object[]数组。
  • toArray(String[]::new):返回一个String数组

Object[] result = stream.toArray();
String[] result = stream.toArray();

  • 将流中的元素收集到另一个目标中,有一个便捷的collect方法可用。它会接受一个Collector接口的实例。Collectors类提供了大量用于生成公共收集器的工厂方法。
  • collect(Collectors.toList()):生成一个列表 ArrayList
  • collect(Collectors.toSet()):生成一个集合 HashSet
  • collect(Collectors.toCollection(Supplier<C> collectionFactory)):生成给定的集合类型

List<String> result = stream.collect(Collectors.toList());
Set<String> result = stream.collect(Collectors.toSet());
TreeSet<String> result = stream.collect(Collectors.toCollection(TreeSet::new));

  • collect(Collectors.joining()):连接流中的所有字符串;
  • collect(Collectors.joining(CharSequence delimiter):通过分隔符连接所有的字符串;
  • collect(Collectors.joining(CharSequence delimiter,CharSequence prefix,CharSequence suffix):使用分隔符连接所有的字符串,将prefix作为生成的字符串的前缀,suffix作为字符串的后缀。

Stream<String> stream = Stream.of("apple","banana");
String result = stream.collect(Collectors.joining()); // result = "applebanana"
result =stream.collect(Collectors.joining(",")); // result = "apple,banana"
result =stream.collect(Collectors.joining(",","prefix","suffix"));
// result = "prefixapple,bananasuffix"

  • 如果想要将流的结果约简为总和、平均值、最大值或最小值,可以使用summarizing(Int|Long|Double)方法中的某一个。这些方法会产生类型为(Int|Long|Double)SummaryStatictics的结果,同时计算总和、数量、平均值、最小值和最大值。

Stream<String> word = Stream.of("apple","banana");
IntSummaryStatistics summary = word.collect(Collectors.summarizingInt(String::length) );
//summary {count=2, sum=11, min=5, average=5.500000, max=6}

  • collect(Collectors.toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper):将其元素收集到映射表中。该方法有两个函数引元,它们用来产生映射表的键和值。
  • collect(Collectors.toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper,BinaryOperator<U> mergeFunction):将其元素收集到映射表中。该方法有三个函数引元,前两个用来产生映射表的键和值,第三个用于合并键值相同的项。
  • collect(Collectors.toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper,BinaryOperator<U> mergeFunction,Supplier<M> mapSupplier):将其元素收集到映射表中。该方法有四个函数引元,前两个用来产生映射表的键和值,第三个用于合并键值相同的项,第四个函数用于指定生成的映射表。
public static class Person{
        private int id;
        private String name;
        public Person(int id ,String name){this.id=id;this.name= name;}
        public int getId() {return id;}
        public String getName() {return name;}
        @Override
        public String toString(){
            return getClass().getName()+"[id="+id+",name="+name+"]";
        }
        public static Stream<Person> people(){
    return Stream.of(new Person(1001,"Tom"),new Person(1002,"Jack"),new Person(1003,"Bob"));
        }
    }
public static void main(String[] args)  {             
        Map<Integer,String> idToName = Person.people().collect(Collectors.toMap(Person::getId,Person::getName));
        System.out.println(idToName);
        //idToName  {1001=Tom, 1002=Jack, 1003=Bob}
        Map<Integer,Person> idToPerson = Person.people().collect(Collectors.toMap(Person::getId,Function.identity()));
        System.out.println("idToPerson  "+idToPerson);
        //idToPerson  {1001=com.company.Main$Person[id=1001,name=Tom], 1002=com.company.Main$Person[id=1002,name=Jack], 1003=com.company.Main$Person[id=1003,name=Bob]}
        Map<Integer,Person>  idToPerson=Person.people().collect(Collectors.toMap(Person::getId,Function.identity(),(oldValue,newValue)->newValue ));
       
        Map<Integer,Person>  idToPerson=Person.people().collect(Collectors.toMap(Person::getId,Function.identity(),(oldValue,newValue)->newValue ,TreeMap::new));
        
    }

  • collect(Collectors.groupingBy(Function<? super T, ? extends K> classifier)):通过classifier函数对流中的元素进行分类。
        Stream<Locale> locals = Stream.of(Locale.getAvailableLocales());
        Map<String,List<Locale>> country = locals.collect(Collectors.groupingBy(Locale::getCountry));
        System.out.println("languageNames:"+country);
        //languageNames:{DE=[de_DE],...}
  • collect(Collectors.partitioningBy(Predicate<? super T> predicate)):通过使用断言函数对流中的元素进行分区。
Map<Boolean,List<Locale>> list = locals.collect(Collectors.partitioningBy(locale -> locale.getLanguage().equals("en")));
        List<Locale> english = list.get(true);
        System.out.println(english);
//[en_US, en_SG, en_MT, en, en_PH, en_NZ, en_ZA, en_AU, en_IE, en_CA, en_IN, en_GB]
  • 下游收集器:是在收集器方法中再次传入一个收集器对象,用于处理groupingBy方法生成的值列表。常用的下游收集器有:
    • counting:返回收集到的元素的个数
    • summing(Int|Long|Double):接受一个引元函数,将该函数应用到下游元素中,并产生他们的和。
    • maxBy和minBy:会接受一个比较器,并产生下游元素中的最大值和最小值。
    • mapping:将函数应用到下游元素,并将函数值传递给另一个收集器。
    • summarizing(Int|Double|Long):如果grouping或partitioningBy的返回值为int,long,double,那么可以将元素收集到汇总统计对象中。


    public static class City{
        private String state;
        private String name;
        private int population;
       public City(String state,String name,int population){
           this.state=state;
           this.name=name;
           this.population=population;
       }
        public String getName() {return name;}
        @Override
        public String toString(){
            return getClass().getName()+"[state="+state+",name="+name+"population="+population+"]";
        }


        public String getState() {
            return state;
        }

        public int getPopulation() {
            return population;
        }
    }
    public static Stream<City> cities(){
        return Stream.of(new City("1001","Tom",1000),new City("1002","Jack",2000),new City("1003","Bob",3000));
    }
    public static void main(String[] args)  {
        Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
        Map<String, Set<Locale>> countryToLocaleSet=locales.collect(groupingBy(Locale::getCountry,toSet()));
        System.out.println("countryToLocals:"+countryToLocaleSet);
        //countryToLocals:{PY=[es_PY], LV=[lv_LV], HR=[hr_HR], DO=[es_DO], UA=[uk_UA], YE=[ar_YE],}

        locales=Stream.of(Locale.getAvailableLocales());
        Map<String,Long> countryToLocaleCounts = locales.collect(groupingBy(Locale::getCountry,counting()));
        System.out.println("countryToLocaleCounts"  +countryToLocaleCounts);
        //countryToLocaleCounts{=46, DE=1, PR=1, HK=1, TW=1, PT=1, ...}
        Stream<City> cities = cities();
        Map<String,Integer> stateToCityPopulation = cities.collect(groupingBy(City::getState,summingInt(City::getPopulation)));
        System.out.println("stateToCityPopulation"+stateToCityPopulation);
        //stateToCityPopulation{1003=3000, 1002=2000, 1001=1000}
        cities=cities();
        Map<String, Optional<String>> stateToLongestCityName = cities.collect(
                groupingBy(City::getState,
                    mapping(City::getName,maxBy(Comparator.comparing(String::length)))
                )
        );
        System.out.println("stateToLongestCityName"+stateToLongestCityName);
        //stateToLongestCityName{1003=Optional[Bob], 1002=Optional[Jack], 1001=Optional[Tom]}
        locales =Stream.of(Locale.getAvailableLocales());
        Map<String,Set<String>> countryToLanguages = locales.collect(
                groupingBy(Locale::getDisplayCountry,
                        mapping(Locale::getDisplayLanguage,toSet()))
        );
        System.out.println("countryToLanguages"+countryToLanguages);
        //countryToLanguages{泰国=[泰文], 巴西=[葡萄牙文],...
        cities=cities();
        Map<String,IntSummaryStatistics> stateTpCityPopulationSummary = cities.collect(groupingBy(
                City::getState,summarizingInt(City::getPopulation)));
          System.out.println("stateTpCityPopulationSummary"+stateTpCityPopulationSummary);
        //stateTpCityPopulationSummary{1003=IntSummaryStatistics{count=1, sum=3000, min=3000, average=3000.000000, max=3000},
        cities=cities();
        Map<String,String> stateToCityNames = cities.collect(groupingBy(
                City::getState,reducing("",City::getName,(s,t)->s.length()==0?t:s+","+t)
        ));
        System.out.println("stateToCityNames"+stateToCityNames);
        //stateToCityNames{1003=Bob, 1002=Jack, 1001=Tom}
        cities=cities();
        stateToCityNames=cities.collect(groupingBy(City::getState,mapping(City::getName,joining(","))));
        System.out.println("stateToCityNames"+stateToCityNames);
        // stateToCityNames{1003=Bob, 1002=Jack, 1001=Tom}


    }

7.基本类型流

将每个整数都包装到包装器对象中是很低效的,对其他基本类型来说,情况也是一样。这些基本类型是:double,float,long,int,short,char,byte和boolean。流库中具有专门的流类型IntStream,LongStream和DoubleStream,用来直接存储基本类型值,而无需使用包装器。如果想要存储short,char,byte和boolean,可以使用IntStream,而对于float,可以使用DoubleStream.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容