java8集合流式操作

1,382 阅读10分钟

java8集合流式操作

获取一个流

创建空流

Stream stream =Stream.empty();

使用集合类的方法

List<String> list = Arrays.asList("a", "b", "c", "d");
Stream listStream = list.stream();                   //获取串行的Stream对象
Stream parallelListStream = list.parallelStream();   //获取并行的Stream对象  

通过of方法

Stream s = Stream.of("test");
Stream s1 = Stream.of("a", "b", "c", "d");

生成无限流

其中第一个方法将会返回一个无限有序值的Stream对象:它的第一个元素是seed,第二个元素是f.apply(seed); 第N个元素是f.apply(n-1个元素的值);生成无限值的方法实际上与Stream的中间方法类似,在遇到中止方法前一般是不真正的执行的。因此无限值的这个方法一般与limit等方法一起使用,来获取前多少个元素。 当然获取前多少个元素也可以使用第二个方法。 第二个方法与第一个方法生成元素的方式类似,不同的是它返回的是一个有限值的Stream;中止条件是由hasNext来断定的。

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f);  
public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)

操作分类

惰性操作

所谓惰性操作就是只描述过程,而不执行的操作

list.stream().filter(i->
                     {
    System.out.println("惰性操作");  
     return  i%3==0;
                     }
                    );

此时执行无输出

及时操作

此时流被耗尽,所以只能有一个及时操作

list.stream().filter(i->
                     {
                         System.out.println("惰性操作");
                         return  i==0;
                     }
                    )
    .forEach(System.out::println);

控制台输出

惰性操作 0 惰性操作 惰性操作 惰性操作 惰性操作 惰性操作

惰性操作

过滤操作

java8提供的筛选操作包括:filter、distinct、limit、skip。

filter

Stream<T> filter(Predicate<? super T> predicate),filter接受一个断言型Predicate接口,我们可以通过这个定义筛选条件

所谓的断言型接口即返回值为boolean类型,当为true时则放入流中

下方就是筛选出小于10的元素

list.stream().filter(i->i<10).forEach(System.out::println);

distinct

distinct基于Object.equals(Object)实现,同时只重写equals()是不够的,还要重写hashCode()。

简单去重

List<Integer> evens = nums.stream()
                        .filter(num -> num % 2 == 0).distinct()
                        .collect(Collectors.toList());

复杂去重

list.stream().distinct().forEach(System.out::println);
  //Person类中重写方法                                                                          
 @Override                                                                      
 public boolean equals(Object obj) {                                            
     return ((Person) obj).age==this.age&&((Person) obj).name.equals(this.name);
 }                                                                              
                                                                                
 @Override                                                                      
 public int hashCode() {                                                        
     return this.name.hashCode()+this.age ;                                     
 }                                                                              

skip

skip操作与limit操作相反,如同其字面意思一样,是跳过前n个元素

通过skip,就会跳过前面两个元素,返回由后面所有元素构造的流,如果n大于满足条件的集合的长度,则会返回一个空的流。

映射

map

举例说明通过map将Person实体映射成为姓名字符串,具体实现如下:

List<String> names = list.stream()    
                            .map(Person::getName).collect(Collectors.toList());

除了上面这类基础的map,java8还提供了mapToDouble(ToDoubleFunction<? super T> mapper)mapToInt(ToIntFunction<? super T> mapper)mapToLong(ToLongFunction<? super T> mapper),这些映射分别返回对应类型的流,java8为这些流设定了一些特殊的操作,比如我们希望计算所有人年龄之和,那么我们可以实现如下:

int totalAge = list.stream()   
                    .mapToInt(Person::getAge).sum();

通过将Student按照年龄直接映射为IntStream,我们可以直接调用提供的sum()方法来达到目的,此外使用这些数值流的好处还在于可以避免jvm装箱操作所带来的性能消耗。

flatMap

flatMap与map的区别在于 flatMap是将一个流中的每个值都转成一个个流,然后再将这些流扁平化成为一个流 。举例说明,假设我们有一个字符串数组String[] strs = {"java8", "is", "easy", "to", "use"};,我们希望输出构成这一数组的所有非重复字符,那么我们可能首先会想到如下实现:

  String[] strs = {"java8", "is", "easy", "to", "use"};          
      Arrays.stream(strs)                                         
              .map(s->s.split(""))                                
              .distinct()                                         
              .collect(Collectors.toList())                       
              .forEach(strings -> {                              
                  System.out.println(Arrays.toString(strings));  
              });                                                
                                                                                                                          

在执行map操作以后,我们得到是一个包含多个字符串(构成一个字符串的字符数组)的流,此时执行distinct操作是基于在这些字符串数组之间的对比,所以达不到我们希望的目的,此时的输出为:

[j, a, v, a, 8]
[i, s]
[e, a, s, y]
[t, o]
[u, s, e]

distinct只有对于一个包含多个字符的流进行操作才能达到我们的目的,即对Stream<String>进行操作。此时flatMap就可以达到我们的目的:

 String[] strs = {"java8", "is", "easy", "to", "use"};
     Arrays.stream(strs)                               
             .map(str->str.split(""))                  
             .flatMap(Arrays::stream)                  
             .distinct()                               
             .forEach(System.out::print);   
             //jav8iseytou

flatMap将由map映射得到的Stream<String[]>,转换成由各个字符串数组映射成的流Stream<String>,再将这些小的流扁平化成为一个由所有字符串构成的大流Steam<String>,从而能够达到我们的目的。 与map类似,flatMap也提供了针对特定类型的映射操作:flatMapToDouble(Function<? super T,? extends DoubleStream> mapper)flatMapToInt(Function<? super T,? extends IntStream> mapper)flatMapToLong(Function<? super T,? extends LongStream> mapper)

take和drop

takeWhile

方法定义

default Stream<T> takeWhile(Predicate<? super T> predicate)

如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。 与Filter有点类似,不同的地方就在当Stream是有序时,返回的只是最长命中序列

 String[] strs = {"t","tasd","atsd","sd","Aqwte"}; 
     Arrays.stream(strs)                           
             .takeWhile(str->str.contains("t"))    
             .forEach(System.out::println);        

输出

t tasd atsd

遇到第一个不符合的就断掉,只把其之前所有的放入流

dropWhile

方法定义

default Stream<T> dropWhile(Predicate<? super T> predicate)

与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream;

  String[] strs = {"t","tasd","atsd","sd","Aqwte"};                                                    
      Arrays.stream(strs)                                                                              
              .dropWhile(str->str.contains("t"))                                                       
              .forEach(System.out::println);                                                           

输出

sd Aqwte

遇到第一个不符合的就断掉,之后的放入流

及时操作

查找操作

allMatch

allMatch用于检测是否全部都满足指定的参数行为,如果全部满足则返回true,例如我们希望检测是否所有的人都已满18周岁,那么可以实现为:

boolean isAdult = list.stream().allMatch(person -> person.getAge() >= 18);

anyMatch

anyMatch则是检测是否存在一个或多个满足指定的参数行为,如果满足则返回true,例如我们希望检测是否有满18岁的,那么可以实现为:

boolean hasWhu = list.stream().anyMatch(person -> person.getAge() >= 18);

noneMathch

noneMatch用于检测是否不存在满足指定行为的元素,如果不存在则返回true,例如我们希望检测是否不存在满18岁的,可以实现如下:

boolean noneCs = list.stream().noneMatch(person -> person.getAge() >= 18);

findFirst

findFirst用于返回满足条件的第一个元素,比如我们希望选出满18岁的第一个人,那么可以实现如下:

Optional<Student> optStu = list.stream().filter(person -> person.getAge() >= 18).findFirst();

findFirst不携带参数,具体的查找条件可以通过filter设置,此外我们可以发现findFirst返回的是一个Optional类型

findAny

findAny相对于findFirst的区别在于,findAny不一定返回第一个,而是返回任意一个,比如我们希望返回任意一个满18岁的人,可以实现如下:

Optional<Student> optStu = list.stream().filter(person -> person.getAge() >= 18).findAny();

实际上对于顺序流式处理而言,findFirst和findAny返回的结果是一样的,当我们启用并行流式处理的时候,查找第一个元素往往会有很多限制,如果不是特别需求,在并行流式处理中使用findAny的性能要比findFirst好。

归约操作

累加

        list.stream().mapToInt(Person::getAge)
                .sum();
        list.stream().map(Person::getAge)
                .reduce(0, (a, b) -> a + b);
        list.stream().map(Person::getAge)
                .reduce(0, Integer::sum);
// 采用无初始值的重载版本,需要注意返回Optional
        list.stream().map(Person::getAge)
                .reduce(Integer::sum)
                .get();

关于reduce

  1. reduce(accumulator) :参数是一个执行双目运算的 Functional Interface ,假如这个参数表示的操作为op,stream中的元素为x, y, z, …,则 reduce() 执行的就是 x op y op z ...,所以要求op这个操作具有结合性(associative),即满足: (x op y) op z = x op (y op z),满足这个要求的操作主要有:求和、求积、求最大值、求最小值、字符串连接、集合并集和交集等。另外,该函数的返回值是Optional的:
Optional<Integer> sum1 = numStream.reduce((x, y) -> x + y);
  1. reduce(identity, accumulator) :可以认为第一个参数为默认值,但需要满足 identity op x = x ,所以对于求和操作, identity 的值为0,对于求积操作, identity 的值为1。返回值类型是stream元素的类型:
Integer sum2 = numStream.reduce(0, Integer::sum);

统计

计数
list.stream().collect(Collectors.counting());
        list.stream().count();
max和min
 list.stream().collect(Collectors.maxBy((p1, p2) -> p1.getAge() - p2.getAge()));
        list.stream().collect(Collectors.maxBy(Comparator.comparing(Person::getAge)));

list.stream().max((p1, p2) ->
                  Integer.valueOf(p1.getAge()).compareTo(p2.getAge()));

同理min

总和

不使用map和reduce实现

list.stream().collect(Collectors.summingInt(Person::getAge));

对应的还有summingLongsummingDouble

平均
 list.stream().collect(Collectors.averagingDouble(Person::getAge));

对应的还有averagingLongaveragingDouble

统计
IntSummaryStatistics collect = list.stream().collect(Collectors.summarizingInt(Person::getAge));
        System.out.println(collect);

对应的还有summarizingLongsummarizingDouble

IntSummaryStatistics{count=100, sum=5021, min=2, average=50.210000, max=99}

收集

字符串拼接
String[] strings = {"sun", "chen", "xi"};
        Arrays.asList(strings)
                .stream()
                .collect(Collectors.joining());
        Arrays.asList(strings)
                .stream()
                .collect(Collectors.joining("|"));
//sun|chen|xi,返回值为String
收集
collect()

通过Collectors.toCollection()可以指定集合类

// collect to Collection
Stream.of("You", "may", "assume").collect(Collectors.toList());
Stream.of("You", "may", "assume").collect(Collectors.toSet());
Stream.of("You", "may", "assume").collect(Collectors.toCollection(TreeSet::new));
toArray()

可以通过参数设置需要结果的类型

Object[] words1 = Stream.of("You", "may", "assume").toArray();
String[] words2 = Stream.of("You", "may", "assume").toArray(String[]::new)
toMap()

toMap : 将stream中的元素映射为 的形式,两个参数分别用于生成对应的key和value的值。比如有一个字符串stream,将首字母作为key,字符串值作为value,得到一个map:

Stream<String> introStream = Stream.of("Get started with UICollectionView and the photo library".split(" "));
Map<String, String> introMap = introStream.collect(Collectors.toMap(s -> s.substring(0, 1), s -> s));

如果一个key对应多个value,则会抛出异常,需要使用第三个参数设置如何处理冲突,比如仅使用原来的value、使用新的value,或者合并:

Stream<String> introStream = Stream.of("Get started with UICollectionView and the photo library".split(" "));
Map<Integer, String> introMap2 = introStream.collect(Collectors.toMap(s -> s.length(), s -> s, (existingValue, newValue) -> existingValue));

如果value是一个集合,即将key对应的所有value放到一个集合中,则需要使用第三个参数,将多个value合并:

Stream<String> introStream3 = Stream.of("Get started with UICollectionView and the photo library".split(" "));
Map<Integer, Set<String>> introMap3 = introStream3.collect(Collectors.toMap(s -> s.length(),
    s -> Collections.singleton(s), (existingValue, newValue) -> {
        HashSet<String> set = new HashSet<>(existingValue);
        set.addAll(newValue);
        return set;
    }));
introMap3.forEach((k, v) -> System.out.println(k + ": " + v));

如果value是对象自身,则使用 Function.identity() ,如:

Map<Integer, Person> idToPerson = people.collect(Collectors.toMap(Person::getId, Function.identity()));

toMap() 默认返回的是HashMap,如果需要其它类型的map,比如TreeMap,则可以在第四个参数指定构造方法:

Map<Integer, String> introMap2 = introStream.collect(Collectors.toMap(s -> s.length(), s -> s, (existingValue, newValue) -> existingValue, TreeMap::new));
分组

返回为Map(Integer,List)

 list.stream()
     .collect(Collectors.groupingBy(Person::getAge))
     .forEach((age,person)-> System.out.println("age:"+age+"person:"+person));
多级分组

上面演示的是一级分组,我们还可以定义多个分类器实现 多级分组,比如我们希望在按学校分组的基础之上再按照专业进行分组,实现如下:

Map<String, Map<String, List<Student>>> groups2 = students.stream().collect(
                Collectors.groupingBy(Student::getSchool,  // 一级分组,按学校
                Collectors.groupingBy(Student::getMajor)));  // 二级分组,按专业

实际上在groupingBy的第二个参数不是只能传递groupingBy,还可以传递任意Collector类型,比如我们可以传递一个Collector.counting,用以统计每个组的个数:

Map<String, Long> groups = students.stream().collect(Collectors.groupingBy(Student::getSchool, Collectors.counting()));

如果我们不添加第二个参数,则编译器会默认帮我们添加一个Collectors.toList()

分区

分区可以看做是分组的一种特殊情况,在分区中key只有两种情况:true或false,目的是将待分区集合按照条件一分为二,java8的流式处理利用collectors.partitioningBy()方法实现分区,该方法接收一个谓词,例如我们希望将学生分为武大学生和非武大学生,那么可以实现如下:

Map<Boolean, List<Student>> partition = students.stream().collect(Collectors.partitioningBy(person->person.getAge()>18);

分区相对分组的优势在于,我们可以同时得到两类结果,在一些应用场景下可以一步得到我们需要的所有结果,比如将数组分为奇数和偶数。