Java Stream API(一)

Stream是JAVA SE8提供的主要用于处理集合的接口,定义是“从支持数据处理操作的源生成的元素序列”。Stream减少了集合操作的复杂程度,同时也实现了“行为参数化”:让方法接受多种行为(或战略)作为参数,并在内部使用,来完成不同的行为,以灵活应变程序的需求变化。

新加入的Stream API给集合操作提供了很大的灵活性,让在JAVA中进行数据处理变得更简洁、优雅。

一个Stream实现类对象类似一个集合,允许改变和获取数据,但是和集合还是有所差别:

  1. Stream本身并不存储元素,元素根据需要产生
  2. Stream操作符不会改变源对象,相反会返回一个持有结果的新Stream
  3. Stream操作符可能是延迟执行的,即可能的等到需要结果的时候才执行

使用Stream不关心具体的循环步骤,即“怎么去做”而只关心“做什么”,一般遵循三个步骤:

  1. 创建一个Stream
  2. 在一个或多个步骤中,指定将一个初始Stream装换为另一个Stream 的中间操作
  3. 使用一个终止操作来产生一个结果。

在操作Stream的过程中,会大量用到Lambda表达式和函数式接口,常见的函数式接口有:

  1. java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。
  2. java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void) 。
  3. java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。
  4. Supplier<T>具有唯一一个抽象方法叫作get,代表的函数描述符是()-> T。
  5. BiFunction<T, U, R>具有唯一一个抽象方法叫作apply,代表的函数描述符是(T,
    U) -> R。

在处理基本类型时,还有专门的IntPredicate等方法,避免拆箱装修带来的性能消耗。

下面介绍使用Stream的步骤:

创建Stream:

List<Integer> list = new ArrayList<>();

//所有的集合对象都可以通过stream方法转化为Stream对象
Stream<Integer> stream = list.stream();
//parallelStream适用于并行的情况
Stream<Integer> stream2 = list.parallelStream();

//数组也可以通过Stream.of方法转化为Stream对象
Integer[] integers = new Integer[]{1,2,3,4,5};
Stream<Integer> stream3 = Stream.of(integers);

//也可以直接通过Stream.of方法构建Stream对象,省略声明数组的部分
Stream<String> stream4 = Stream.of("a","b","c");

//构建空的Stream对象
Stream<String> stream5 = Stream.empty();

除了这些方法以外,还有比较特殊的构建方法,可以用来生成“无限长度的流”:

//generate(Supplier<T> s)方法,参数为Supplier接口的实现对象,Supplier是一个函数式接口
//构建一个含有常量值的Stream
Stream.generate(()->"echo");
//构建一个含有随机数字的Stream
Stream.generate(Math::random);
//iterate方法,参数为UnaryOperator(一元运算符)接口
//根据需要构建无限长度的序列:0,1,2,3,4,5...
Stream.iterate(BigInteger.ZERO,n->n.add(BigInteger.ONE));

其中iterate方法可以用来生成“一系列的值”。比如用iterate方法生成斐波纳契元组序列:

Stream.iterate(new int[]{0,1},ints -> new int[]{ints[1],ints[0]+ints[1]})
                .limit(20)
                .forEach(t-> System.out.println(t[0]+","+t[1]));

JAVA SE8中的许多其他类也添加了提供Stream 的方法,比如:

//根据正则表达式来拆分字符串为Stream
Stream<String> words = Pattern.compile("[\\P{L}]+").splitAsStream("InputStream");

中间操作:

一、Stream转换

Stream转换为从一个Stream中读取数据,然后将转换后的数据写入到另一个新的Stream里面。

  1. 过滤器转换:filter方法
//filter方法的参数是一个Predicate<T>对象,需要返回一个boolean值

//创建一个Stream
Stream<String> words = Stream.of("aadfasdf","bqwerqw","caa");

//使用filter方法筛选长度大于3的字符串
Stream<String> longWords = words.filter(w -> w.length()>3);

     2. 形式转换(对Stream中的每一个元素应用同一个函数,并将每一个元素返回的唯一结果写到新的Stream里):map方法

//map方法的参数是Function接口
Stream<String> words = Stream.of("adfa","adfa","zxcv","qewr","erte");
//map对Stream中的每一个元素进行指定的转化操作,这里把所有元素变成"a"
System.out.println(Arrays.toString(words.map(x->"a").toArray()));

//Stream对象会在一串操作结束或被关闭,需要重新声明
words = Stream.of("adfa","adfa","zxcv","qewr","erte");
//将所有元素转为大写
System.out.println(Arrays.toString(words.map(String::toUpperCase).toArray()));

       3. 形式装换多返回值(对Stream中的每一个元素应用同一个函数,并将每一个元素返回的多个结果写到新的Stream里):flatMap方法

//flatMap方法的参数是Function接口
Stream<String> words = Stream.of("adfa","adfa","zxcv","qewr","erte");
//把words拆成{"a""d""f"...}
//和map的差别在于,map会产生一个Stream<Stream<Character>>对象
Stream<Character> result =
        words.flatMap(
                new Function<String, Stream<? extends Character>>() {
                    @Override
                    public Stream<? extends Character> apply(String s) {
                        List<Character> result = new ArrayList<>();
                        for (char c:s.toCharArray()) result.add(c);
                        return result.stream();
                    }
                }
        );
System.out.println(Arrays.toString(result.toArray()));

flatMap实际上提供的是“流扁平化操作”,即将多个流合并为一个流的操作。

比如给定两个数字列表,如何返回所有的数对呢?例如,给定列表[1, 2, 3]和列表[3, 4],应该返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]。

List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3, 4);
List<int[]> pairs =numbers1.stream()
                      .flatMap(
                               i -> numbers2.stream()
                                        .map(j -> new int[]{i, j})
                      )
                      .collect(toList());
pairs.stream().map(Arrays::toString).forEach(System.out::print);

二、提取子流和组合流

//Stream.limit(n)会返回一个包含n个元素的新流

//Stream.skip(n)会丢弃掉前面的n个元素

//Stream.concat(Stream s1,Stream s2)拼接两个流

//Stream.peek返回相同的流,但是在每次获取一个元素时会执行指定的方法。peek的参数为Function接口
//注意Stream不会按照元素的调用顺序执行,在这个例子中,只有执行到limit方法时,才会回头执行iterate
Object[] powers = Stream.iterate(1.0,p->p*2)
          .peek(e->System.out.println("Fetching "+e))
          .limit(20).toArray();

三、依赖于被操作流的转换

//distinct()方法,筛选去除重复元素
Stream<String> stringStream = Stream.of("A", "A", "A", "A", "B").distinct();

//sorted()方法,在产生任何元素之前进行排序,参数为Compartor接口
Stream<String> words = Stream.of("adfa","adfa","zxcv","qewr","erte");
Stream<String> stringStream2 = words.sorted(String::compareTo);

终止操作:

当一个流使用了终止操作后,就不能再应用其他方法了。

一、简单的聚合方法

所谓“聚合”和数据库SQL中的概念相同,即把多个值“聚合”成一个值

注意“聚合”都是终止操作,在使用聚合方法之后,Stream对象会被关闭不能再进行其他操作

  • min、max方法都接收一个Compartor接口实现,并返回一个Optional<T>值,它可能封装返回值,也可能表示没有返回值(当流为空时)。
Stream<String> words = Stream.of("adfa","adfa","zxcv","qewr","erte");
//聚合求最大值,min方法类似,参数是一个Comparator实例
Optional<String> largest = words.max(String::compareToIgnoreCase);
//Optional对象调用get()方法,如果值不存在不会报null pointer,但是会报NoSuchElement
if (largest.isPresent()){
    System.out.println("largest:"+largest.get());
}
  • findFirst方法返回非空集合中的第一个值(Optional<T>),通常和filter配合使用:
words = Stream.of("adfa","adfa","zxcv","qewr","erte");
Optional<String> firstElementStartsWithA = words.findFirst().filter(s->s.startsWith("a"));
if (firstElementStartsWithA.isPresent()){
    System.out.println(firstElementStartsWithA.get());
}
  • findAny方法,在并行计算中返回找到的第一个(并不一定是“排序上的第一个”),通常和filter配合使用:
Stream<String> words = Stream.of("adfa","adfb","zxcv","qewr","erte");
//findFirst一定返回adfa,而findAny可能返回adfa也可能返回adfb
Optional<String> startsWithA =
    words.parallel().filter(s->s.startsWith("a")).findFirst();
    System.out.println(startsWithA.get());
}
  • anyMatch方法,判断是否存在符合项,接收一个Predicate对象,返回boolean。allMatch和noneMatch类似,分别在所有元素都符合和没有元素符合时返回true。
Stream<String> words = Stream.of("adfa", "adfb", "zxcv", "qewr", "erte");
boolean hasWordStartWithA = words.parallel().anyMatch(s -> s.startsWith("a"));
System.out.println(hasWordStartWithA);
  • reduce方法接收一个BiFunction二元函数对象,依次处理每两个元素
Stream<Integer> values = Stream.of(1,2,3);
//这里会返回6
Optional<Integer> sum = values.reduce((x,y)->x+y);

//可以设定一个对结果没有影响的标志值,这样可以直接返回T类型二无需对Optional进行处理
Integer sum = values.reduce(0,Integer::sum);

Stream<String> words = Stream.of("adfasdf","adsfa","ghjk","yioyui");
//如果出现处理的不是T,而不是T的一个属性这种情况,需要额外声明一个"累加器"函数
int result = words.reduce(0,(total,word)->total+word.length(),(total1,total2)->total1+total2);

//reduce也可以用来求最大值和最小值
Optional<Integer> maxValue = values.reduce(Integer::max);
Optional<Integer> minValue = values.reduce(Integer::min);

map和reduce组合通常被称为map-reduce模式,这种模式极易实现并行化。

二、收集输出结果

注意下面提到的收集结果的方法同样会终止Stream

  • toArray()方法
Stream<String> words = Stream.of("adfasdf","adsfa","ghjk","yioyui");
//toArray方法,传入需要输出的数组类型,得到数组
String[] strings = words.toArray(String[]::new);
  • iterator()方法
//返回一个Iterator<T>对象
Iterator<String> iterator = words.iterator();
while (iterator.hasNext()){
    String str = iterator.next();
}
  • collect()方法,把结果输出到set或者list,或者
//输出到List
List<String> list = words.collect(Collectors.toList());
Set<String> set = words.collect(Collectors.toSet());
//也可以指定输出的list、set的类型
TreeSet<String> treeSet = words.collect(Collectors.toCollection(TreeSet::new));

//对于字符串,还可以直接把字符串串联起来
String str = words.collect(Collectors.joining());
String str = words.collect(Collectors.joining(","));

            Collectors还为“汇总”(求和、求平均等)提供了一系列的操作,例如:

           1、counting方法,获取元素个数,maxBy获取最大值,minBy获取最小值

           2、summing(Int|Double|Long)方法,对指定的对象的属性进行求和

           3、averageing(Int|Double|Long)方法,对制定的对象的属性进行求平均

           4、Collectors还有summerizing(Int|Double|Long)方法,可以一次性得到上述的所有统计结果,然后通过getXX()方法获取值

Stream<Integer> numbers = Stream.of(1,2,3,4);
IntSummaryStatistics collect = numbers.collect(Collectors.summarizingInt(Integer::intValue));
long sum = collect.getSum();
double average = collect.getAverage();
int max = collect.getMax();
long count = collect.getCount();
  • 使用collect(Collectors.toMap())把结果转化成map
Person person1 = new Person(1,"cai");
Person person2 = new Person(2,"li");
Person person3 = new Person(3,"wang");
Stream<Person> personStream = Stream.of(person1,person2,person3);
Map<Integer,Person> idToPerson =
        //toMap()方法接收两个Function对象
        personStream.collect(Collectors.toMap(Person::getId,p->p));

//如果出现由重复Key的情况,多传入一个BiFunction对象
Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
Map<String,String> languageNames = locales.collect(
        Collectors.toMap(
                l->l.getDisplayLanguage(),
                l->l.getDisplayLanguage(l),
                //设置当出现两个相同的Key的时候只保留第一个
                (l1,l2)->l1
        )
);

        toMap()方法要求不能有重复的Key,否则就需要额外定义一个函数进行处理。对“分组”操作非常不便。Collectors的groupingBy、partitionBy方法解决了这个问题:

Person person1 = new Person(1,"cai",true);
Person person2 = new Person(1,"li",true);
Person person3 = new Person(2,"wang",false);
Stream<Person> personStream = Stream.of(person1,person2,person3);
//接收一个Function,为分类函数
Map<Integer, List<Person>> collect = personStream.collect(Collectors.groupingBy(Person::getClassId));
//如果分类函数的返回值是boolean(即一个Predicate对象),则用partitionBy效率更高
personStream.collect(Collectors.partitioningBy(Person::isMale));

        groupingBy()方法还可以嵌套一个groupiongBy()进行二级分组。

Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel =
    menu.stream().collect(
            groupingBy(
                      Dish::getType,
                      groupingBy(dish -> {
                         if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                         else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                         else return CaloricLevel.FAT;} )
            )
    );

      groupingBy()方法可以嵌套其他归约函数,作为Map的key的值。实际上groupingBy()本身就是groupingBy(f,toList())的简便写法。

//将最大的calory作为值存入
//collectingAndThen方法用于转换返回值的类型
Map<Dish.Type, Dish> mostCaloricByType =
               menu.stream().collect(groupingBy(Dish::getType,
                             collectingAndThen(
                                 maxBy(comparingInt(Dish::getCalories)),
                             Optional::get)));

//和summingInt方法联合使用
Map<Dish.Type, Integer> totalCaloriesByType =
           menu.stream().collect(groupingBy(Dish::getType,
                                 summingInt(Dish::getCalories)));


//使用toSet()收集
Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType =
              menu.stream().collect(
                     groupingBy(Dish::getType, mapping(
                     dish -> { if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                     else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                     else return CaloricLevel.FAT; },
              toSet() )));

partitionBy方法也有类似的用法。        

三、返回void的终止方法

forEach是一个返回void的终端操作,它会对源中每一个元素应用一个Lambda。例如:

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

 

转载于:https://my.oschina.net/pierrecai/blog/853509

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值