因为学习了Lambda表达式,再学习Java8新特性之Stream的API常用函数使用及说明,两者结合起来,代码就可以玩得更溜,下面做Stream的一些常用函数使用及说明,方日后温习,如果发现有不足之处,请多多指教。
什么是Stream(流)?
Stream是数据渠道,用于操作数据源(如:集合,数组等)转成的元素序列,对集合中的每个元素进行一系列并行或者串行的流水线操作。
Stream的作用是什么(为什么需要Stream)?
Stream是Java8的一大亮点,它与java.io包中的InputStream、OutputStream是完全不同的概念;也不同于解析XML出来的Stream。
Stream是java对集合对象功能的增强,对集合对象进行各种非常方便、高效的聚合操作,或者对大指数据操作。Stream的API同样借助于Lambda表达式(支持函数式编程,代码非常简洁),提高编程效率和程序的可读性,同时它还提供了并行和串行这两种模式进行汇聚操。默认情况能充分利用cpu的资源(Stream操作是延迟执行的,需要结果的时候才执行)。
Stream操作的三步曲
- 创建Stream:通过一个数据源(如:数组,集合),获到一个流。
- 中间操作:一个中间操作链(如:filter,map等),对数据源的数据进行处理。
- 终止操作:一个终止操作,执行中间操作链,并产生结果。
Stream创建的四种方式
-
Collection提供了两个方法,分别为stream()和parallelStream()。
//通过List集合创建Stream List<String> list = new ArrayList<>(); Stream<String> stream1 = list.stream();//获取一个顺序流 Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
-
通过Arrays中的静态方法stream()获取一个数组流。
//通过Arrays中的静态方法stream()获取一个数组流 Student[] students = new Student[2]; Stream<Student> stream = Arrays.stream(students);
-
通过Stream类中的静态方法of()。
注意:这里可以是字符串,数组,集合等其他数据类型。
//通过Stream类中的静态方法of()。注意:这里可以是字符串,数组,集合等其他数据类型。 Stream<String> stream3 = Stream.of("java", "lambda", "stream", "6666");
-
创建无限流。
//使用iterate()创建无限流,这个通常和limit()一起使用,限制流中元素的个数。 //iterate()接受一个种子值,和一个UnaryOperator(例如 f)。然后种子值成为Stream的第一个元素, //f(seed) 为第二个,f(f(seed)) 第三个,以此类推. Stream<Integer> iterate = Stream.iterate(0, x -> x + 1); iterate.limit(5).forEach(System.out::println); //使用generate()创建无限流,通常跟limit()一起使用,限制流中元素的个数。 //可以根据任何计算方式来生成,通过实现Supplier接口,你可以自己来控制流的生成。 Stream<Long> generate = Stream.generate(new Supplier<Long>() { long a = 1, b = 2; @Override public Long get() { long temp = a + b; a = b; b = temp; return a; } }); generate.limit(10).forEach(System.out::println);
Stream的中间操作
中间操作符(如:filter,map等),多个中间操作可以连接起来形成一条流水线形式对数据源的数据进行处理。注意:如果未触发终止操作
(下面会进行介绍),中间操作是不会执行任何处理,在触发终止操作时会一次性全部执行中间操作链并产生结果。
中间操作有状态之分:无状态——指元素的处理不受之前元素的影响;有状态——指该操作只有拿到所有元素之后才能继续下去。
Stream的终止操作(结束操作)
一个终止操作,执行中间操作链,并产生结果。终止操作有状态之分:非短路——指必须处理所有元素才能得到最终结果;短路——指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。
Stream的实际操作
我们了解了Java8新特性Stream的API后,开始以代码实践操作一遍。
一、中间操作就是对容器(集合)的处理过程,包括:筛选(filter、limit、distinct、skip...),映射(map、flatMap...),排序(sorted...),消费(peek..)等 。先写一个学生科目分数对象,以便操作。
public class StudentSubject {
private String studentName;
private String subject;
private Integer score;
public StudentSubject(String studentName, String subject, Integer score) {
this.studentName = studentName;
this.subject = subject;
this.score = score;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
@Override
public String toString() {
return "StudentSubject{" +
"studentName='" + studentName + '\'' +
", subject='" + subject + '\'' +
", score=" + score +
'}';
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
final StudentSubject ss = (StudentSubject) obj;
if (this == ss) {
return true;
} else {
return (this.studentName.equals(ss.studentName));
}
}
@Override
public int hashCode() {
int hash_code = 7;
hash_code = 13 * hash_code+ (studentName == null ? 0 : studentName.hashCode());
return hash_code;
}
}
注意:因为中间操作链在没有执行终止操作前是不会执行的,所有下面都是以执行forEach终止操作来验证中间操作链的使用。
筛选(filter、limit、distinct、skip...)
-
filter(),过滤掉某些元素。如:获取大90的科目信息
StudentSubject s1 = new StudentSubject("张三", "语文", 85); StudentSubject s2 = new StudentSubject("小李", "语文", 91); StudentSubject s3 = new StudentSubject("张三", "数学", 95); StudentSubject s4 = new StudentSubject("小李", "数学", 95); StudentSubject s5 = new StudentSubject("张三", "英语", 92); List<StudentSubject> list = new ArrayList<>(); list.add(s1); list.add(s2); list.add(s3); list.add(s4); list.add(s5); //获取大90的科目信息 list.stream().filter(item->item.getScore()>90).forEach(System.out::println); //输出的结果为 StudentSubject{studentName='小李', subject='语文', score=91} StudentSubject{studentName='张三', subject='数学', score=95} StudentSubject{studentName='小李', subject='数学', score=95} StudentSubject{studentName='张三', subject='英语', score=92}
-
limit(),截断流,使其元素不超过给定的数量。
//截流,取前面三个元素信息 list.stream().limit(3).forEach(System.out::println); //打印结果 StudentSubject{studentName='张三', subject='语文', score=85} StudentSubject{studentName='小李', subject='语文', score=91} StudentSubject{studentName='张三', subject='数学', score=95}
-
distinct(),通过流所生成的元素的hashCode()和equals()去除重复元素。
注意:这里有坑,要让distinct起作用,就必须在对应实体类中重写HashCode和equals方法。
//通过流所生成的元素的hashCode()和equals()去除重复元素。 list.stream().distinct().forEach(System.out::println); //打印结果 StudentSubject{studentName='张三', subject='语文', score=85} StudentSubject{studentName='小李', subject='语文', score=91} List<String> stringList = Arrays.asList("aa","bb","cc","aa","bb","cc"); stringList.stream().distinct().forEach(System.out::println); //打印结果 aa bb cc
-
skip(), 跳过元素,返回一个扔掉了前N个元素的流。 如果流中元素N个,则是返回一个空流。与limit(n)互补。
//扔掉前面三个元素 list.stream().skip(3).forEach(System.out::println); //打印结果 StudentSubject{studentName='小李', subject='数学', score=95} StudentSubject{studentName='张三', subject='英语', score=92}
映射(map、flatMap...)
-
map(),接收一个函数作为参数,该函数会被应用到每一个元素上,并将其他映射成一个新的元素。
//>=95分的就输入科目信息,否则就为null list.stream().map(item->{ if(item.getScore()>=95){ return item; } return null; }).forEach(System.out::println); //打印结果 null null StudentSubject{studentName='张三', subject='数学', score=95} StudentSubject{studentName='小李', subject='数学', score=95} null //>=95分的就输入true,否则就为false list.stream().map(item->item.getScore()>=95).forEach(System.out::println); //打印结果 false false true true false //写法2 涉及到collect终止操作,下面会详细说。 //>=95分的就输入科目信息,否则就为null List<StudentSubject> studentSubjectStream = list.stream().map(item -> { if (item.getScore() >= 95) { return item; } return null; }).collect(Collectors.toList()); studentSubjectStream.forEach(System.out::println); //>=95分的就输入true,否则就为false List<Boolean> booleanStream = list.stream().map(item -> item.getScore() >= 95) .collect(Collectors.toList()); booleanStream.forEach(System.out::println);
-
mapToInt
//mapToInt 跟map都类似的,只是类型被限定为IntStream。 IntSummaryStatistics intSummaryStatistics = list.stream().mapToInt(person-> person.getScore()).summaryStatistics(); System.out.println("最大分数:"+intSummaryStatistics.getMax()); //最大值 System.out.println("最小分数:"+intSummaryStatistics.getMin()); //最小值 System.out.println("分数总和:"+intSummaryStatistics.getSum()); //总计 System.out.println("信息条数:"+intSummaryStatistics.getCount());//个数 System.out.println("平均分数:"+intSummaryStatistics.getAverage());//平均数返回的是double类型
mapToLong、mapToDouble 跟map都类似的,只是类型被限定为不同类型而已。这里就不举例了。注意:没有float类型。
-
flatMap(..) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。这个过程也称为元素拍平拍扁。
//把每学生科目分数信息转成一个新有流 list.stream().flatMap(item->{ //将每个元素转换成一个stream String[] info = {item.getStudentName()+"_"+item.getSubject()+"_"+item.getScore()}; Stream<String> newStream = Arrays.stream(info); return newStream; }).forEach(System.out::println); //打印结果 张三_语文_85 小李_语文_91 张三_数学_95 小李_数学_95 张三_英语_92
-
flatMapToInt() 跟flatMap的作用一样 只是类型被限定为IntStream。
//flatMapToInt(..) 和 flatMap的作用一样 只是类型被限定为IntStream IntStream intStream =list.stream() .flatMapToInt(item ->IntStream.of(item.getScore())); intStream.forEach(System.out::println); //打印结果 85 91 95 95 92
- faltMapToLong、faltMapToDouble 跟faltMap都类似的,只是类型被限定为不同类型而已。这里就不举例了。注意:没有float类型。
排序(sorted...)
-
sorted(), 排序 底层依赖Comparable 实现,也可以提供自定义比较器。
//sorted(), 排序,底层依赖Comparable实现,也可以提供自定义比较器。 //底层依赖Comparable Stream.of(1,1,0,8,2,4,6).sorted().forEach(System.out::println); list.stream() //自定义比较器 按分数由高到低 .sorted((ss1,ss2)->ss1.getScore()<ss2.getScore()?1:ss1.getScore()==ss2.getScore()?0:-1) .forEach(System.out::println); //打印结果 0 1 1 2 4 6 8 StudentSubject{studentName='张三', subject='数学', score=95} StudentSubject{studentName='小李', subject='数学', score=95} StudentSubject{studentName='张三', subject='英语', score=92} StudentSubject{studentName='小李', subject='语文', score=91} StudentSubject{studentName='张三', subject='语文', score=85}
消费(peek...)
-
peek() 挑选,如同于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,没有返回值。可以理解为提前消费。
//所有同学所有科目都是100分,哈哈 list.stream().peek(item->item.setScore(100)).forEach(System.out::println); //打印结果 StudentSubject{studentName='张三', subject='语文', score=100} StudentSubject{studentName='小李', subject='语文', score=100} StudentSubject{studentName='张三', subject='数学', score=100} StudentSubject{studentName='小李', subject='数学', score=100} StudentSubject{studentName='张三', subject='英语', score=100}
二、终止操作,执行中间操作链,并产生结果。当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。之后如果想要操作就必须新打开流。终止操作包括:循环遍历操作(forEach、forEachOrdered)、收集操作(collect..)、 匹配与聚合操作(allMatch、max、min...)
循环遍历操作
-
forEach()、forEachOrdered(),遍历操作 ,对每个数据遍历迭代。
forEach()在上面的例子已使用过,就不举例了。注意:forEach是一个终端操作,参数也是一个函数,它会迭代中间操作完成后的每一个数据。
forEachOrdered适用于并行流的情况下进行迭代,能确保迭代的有序性。如下面例子:
Stream.of(1,3,7,5,4,9,8,-1) .parallel() .forEachOrdered(item-> System.out.println(Thread.currentThread().getName()+": "+item)); //打印的结果 ForkJoinPool.commonPool-worker-4: 1 ForkJoinPool.commonPool-worker-2: 3 ForkJoinPool.commonPool-worker-2: 7 ForkJoinPool.commonPool-worker-2: 5 ForkJoinPool.commonPool-worker-2: 4 ForkJoinPool.commonPool-worker-2: 9 ForkJoinPool.commonPool-worker-2: 8 ForkJoinPool.commonPool-worker-2: -1
parallel()提供并行操作的支持。并行这一块知识,先不在这里深入。
收集操作
-
collect(),收集操作,接收一个Collector接口的实现,将流转换为其他形式(收集到List,Set,Map等容器中)。
//collect:接收一个Collector实例,将流中元素收集成另外一个数据结构。 System.out.println("==========将分数 装成list========="); //将分数 装成list List<Integer> scoreList = list.stream().map(StudentSubject::getScore) .collect(Collectors.toList()); scoreList.forEach(System.out::println); System.out.println("=========转成set=========="); //转成set Set<Integer> scoreSet = list.stream().map(StudentSubject::getScore) .collect(Collectors.toSet()); scoreSet.forEach(System.out::println); System.out.println("=========转成map=========="); //转成map,注:key不能相同,否则报错 Map<String,Integer> map = list.stream() .collect(Collectors.toMap(StudentSubject::getSubject, StudentSubject::getScore)); map.forEach((k,v)->System.out.println(k+":"+v)); System.out.println("=========字符串分隔符连接=========="); //字符串分隔符连接 String subjectName = list.stream().map(StudentSubject::getSubject) .collect(Collectors.joining(",", "(", ")")); System.out.println(subjectName); //打印结果 ==========将分数 装成list========= 85 95 92 =========转成set========== 85 92 95 =========转成map========== 数学:95 语文:85 英语:92 =========字符串分隔符连接========== (语文,数学,英语)
注意下面的两种写法的不同之处。
//写法一 Stream.of("java", "ios", "android", "h5", "rn") .collect(Collectors.toSet()) //set 容器 .forEach(e -> System.out.println(e)); //写法二 Set<String> setList = Stream.of("java", "ios", "android", "h5", "rn") .collect(Collectors.toSet()) ; setList.forEach(e -> System.out.println(e));
写法一中collect和forEach同时使用了终止操作符,大家都会想到终止操作不是只能用一次就终止了么?其实forEach不仅仅是Stream中得操作符还是各种集合中得一个语法糖。
匹配、聚合操作
-
allMatch:匹配操作,接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
noneMatch:匹配操作,接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
anyMatch:匹配操作,接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
findFirst:查找操作,返回流中第一个元素
findAny:查找操作,返回流中的任意元素
count:聚合操作,返回流中元素的总个数
max:匹配操作,返回流中元素最大值
min:匹配操作,返回流中元素最小值List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); boolean allMatch = list.stream().allMatch(e -> e > 10); System.out.println("allMatch: "+allMatch); boolean noneMatch = list.stream().noneMatch(e -> e > 10); System.out.println("noneMatch: "+noneMatch); boolean anyMatch = list.stream().anyMatch(e -> e > 5); System.out.println("anyMatch: "+anyMatch); Integer findFirst = list.stream().findFirst().get(); System.out.println("findFirst: "+findFirst); Integer findAny = list.stream().findAny().get(); System.out.println("findAny: "+findAny); long count = list.stream().count(); System.out.println("count: "+count); Integer max = list.stream().max(Integer::compareTo).get(); System.out.println("max: "+max); Integer min = list.stream().min(Integer::compareTo).get(); System.out.println("min: "+min); //打印结果 allMatch: false noneMatch: true anyMatch: true findFirst: 1 findAny: 1 count: 9 max: 9 min: 1
归约操作
-
reduce(),归约操作,把整个数据流的值归约为一个值(比如对所有元素求和,乘啊等)。(如:count、max、min操作的底层就是运用了归约操作)
Optional reduce(BinaryOperator accumulator):
第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。
T reduce(T identity, BinaryOperator accumulator):
流程跟上面一样,只是第一次执行时,accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。
U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator combiner):
在串行流(stream)中,该方法跟第二个方法一样,即第三个参数combiner不会起作用。在并行流(parallelStream)中,我们知道流被fork join出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity,accumulator)一样,而第三个参数combiner函数,则是将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(accumulator)流程进行归约。
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); Integer v = list.stream().reduce((x1, x2) -> x1 + x2).get(); System.out.println("v:"+v); Integer v1 = list.stream().reduce(10, (x1, x2) -> x1 + x2); System.out.println("v1:"+v1); Integer v2 = list.stream().reduce(0, (x1, x2) -> { System.out.println("stream accumulator: x1:" + x1 + " x2:" + x2); return x1 - x2; }, (x1, x2) -> { System.out.println("stream combiner: x1:" + x1 + " x2:" + x2); return x1 * x2; }); System.out.println("v2:"+v2); //并行流 Integer v3 = list.parallelStream().reduce(0, (x1, x2) -> { System.out.println("parallelStream accumulator: x1:" + x1 + " x2:" + x2); return x1 - x2; }, (x1, x2) -> { System.out.println("parallelStream combiner: x1:" + x1 + " x2:" + x2); return x1 * x2; }); System.out.println("v3:"+v3); //打印的结果 v:120 v1:130 v2:-120 v3:-2004310016
转换操作
-
toArray(),转成数组,可以提供自定义数组生成器。
Object aa[] = Stream.of("android","java","IOS").toArray(); StudentSubject s1 = new StudentSubject("张三", "语文", 85); StudentSubject s2 = new StudentSubject("张三", "数学", 95); StudentSubject s3 = new StudentSubject("张三", "英语", 92); StudentSubject s4 = new StudentSubject("张三", "物理", 92); List<StudentSubject> list = Arrays.asList(s1,s2,s3,s4); //调用的stream的toArray的函数 String[] ss = list.stream().toArray(str -> new String[list.size()]); String[] ss1 = list.stream().toArray(String[]::new); Object[] obj1 = list.stream().toArray(); //直接调用的List接口的toArray函数 String[] ss2 = list.toArray(new String[list.size()]); Object[] obj2 = list.toArray();
前三个,是调用的stream的toArray的函数,后面的两个,是直接调用的List接口的toArray函数。
总结
- 了解Stream含义
- 了解Stream的作用
- 了解Stream的操作步骤
- 对Stream的Api的常用函数进行操作及汇总
注意
- 集合讲的是数据,流讲的计算。
- Stream本身不会存储元素。
- Stream不会改变源对象,但它会返回一个持有结果的新Stream。
- Stream操作是延迟执行的。这就意味着他们会等到需要结果的时候才执行。