Stream初次使用

前段时间唠嗑时,朋友向我推荐了java8的stream,说是特别好用,简化了很多代码,话说Java8的特性都已经出来很久了,新的都要出现了,却一直没有跟上进度,于是最近开始研究stream,话不多说这就开始。

首先是在网上翻阅一些博客整理出来的知识点:
此处参考博文:菜鸟教程:http://www.runoob.com/java/java8-streams.html

Stream使用了一种类似sql语句从数据库查询数据的直观方式来提供一种对java集合运算和表达的高阶抽象。
这种风格将要处理的元素集合看做一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选、排序、聚合等。
元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结果。

在这里插入图片描述

Stream是什么

Stream不是某种数据结构,它是一个来自数据源的元素队列并支持聚合操作,是数据源的一种视图
①数据源:流的来源,可以使集合、数组、I/O channel、产生器generator等
②元素:是特定类型的对象,形成一个队列。Java中的stream并不会存储元素,而是按需计算
③局和操作:类似sql语句一样的操作,比如filter、map、reduce、match、sorted等

1.stream不存储数据
Stream不是一种数据结构,只是某种数据源的一个视图,数据源可以是一个数组,java容器或I/O channel等
2.stream为函数式编程而生,不改变源数据
对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是产生一个不包含被过滤元素的新stream
3.stream惰式执行,即延迟执行特性
stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行
4.Stream可消费性
Stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成
在这里插入图片描述

在这里插入图片描述
Stream与conllection不同的基础特征:
①Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
②内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

此处参考博文:Java Stream API入门篇:http://www.cnblogs.com/CarpenterLee/p/6545321.html

stream解决了什么

1.可以直接实现想要什么
2.代码简洁 不用再使用for循环,减少代码
3.可以进行并行处理
并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程,并使代码简化且高性。

Stream怎么用

1、生成流
常用的创建stream有两种途径
1)通过stream接口的静态工厂方法
2)通过Collection接口的默认方法-stream(),把集合对象转换成stream
① stream() 为集合创建串行流

List<Offer> offerList = offerService.findBySaleId(971,0,0);
offerList.stream();

②parallelStream() 为集合创建并行流

List<Offer> offerList = offerService.findBySaleId(971,0,0);
offerList.parallelStream();

查看源码:

public interface Collection<E> extends Iterable<E> {
	/** 忽略其他方法**/
	/** 
	在此集合中的元素上创建一个Spliterator。
	Spliterators是一个分割迭代器,是为了并行遍历元素而设计的一个迭代器,jdk1.8中的集合框架中的数据结构都默认实现了spliterator
	相关可学习:JDK8源码之Spliterator并行遍历迭代器:https://blog.csdn.net/lh513828570/article/details/56673804
	**/
	@Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }
  
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    
     default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
}

可查看stream()和parallelStream()的方法发现,均调用了StreamSupport的stream方法,仅参数parallel的值不同,true为并发
_类StreamSupport 用于创建和操作流的低级实用程序方法
该类主要用于库编写者,提供数据结构的流视图; 针对最终用户的大多数静态流方法都在各种Stream类中。
所以,StreamSupport.stream是将集合真正转化为流的地方
_
在这里插入图片描述

2、中间操作
Filter:设置条件过滤元素,类似sql语句中的where条件进行筛选

List<Student> studentList = getStudentList();
long c1 = studentList.stream().filter(student -> student.getAge()>7).count();
long c2 = studentList.stream().filter(student -> {
    return  student.getGrade() == 1 && student.getSex().equals("女");
}).count();
System.out.println("c1:"+c1+";c2:"+c2);

Sorted:对流进行排序
①sorted() 默认使用自然排序,其中的元素必须实现Comparable接口
②sorted(Comparator<? super T> comparator) 可以根据某个实例排序
sorted(Comparator.reverseOrder() 自然逆序
sorted(Comparator.comparing(Student::getAge)) 根据某属性排序
sorted(Comparator.comparing(Student::getAge).reversed()) 根据某属性排序后逆序

例子:

Stream<Integer> integerStream = Stream.of(1,2,3,2,5,1);
/**自然排序**/
System.out.println("---Sorting Natural---");
integerStream.sorted().forEach(i -> System.out.printf(i+","));
/**自然逆序**/
System.out.println("---Sorting reverse---");
integerStream.sorted(Comparator.reverseOrder()).forEach(i -> System.out.printf(i+","));

以上代码会报错,java.lang.IllegalStateException: stream has already been operated upon or closed,
因为流只能被消费一次,在integerStream.sorted()时已经使用过一次,故在integerStream.sorted(Comparator.reverseOrder())这里属于二次使用,便会报已关闭的错误。改成一下写法则正确:

String[] arr = new String[]{"1","2","1","2","5","1"};
/**自然排序**/
System.out.println("---Sorting Natural---");
Arrays.stream(arr).sorted().forEach(i -> System.out.printf(i+","));
/**自然逆序**/
System.out.println("\n---Sorting reverse---");
Arrays.stream(arr).sorted(Comparator.reverseOrder()).forEach(i -> System.out.printf(i+","));

根据实体的某一属性进行排序

System.out.println("---Sorting using Comparator by Age---");
studentList.stream().sorted(Comparator.comparing(Student::getAge)).collect(Collectors.toList()).forEach(student -> System.out.println(student.getName()+"-"+student.getAge()));
System.out.println("---Sorting using Comparator by Age with reverse order---");
studentList.stream().sorted(Comparator.comparing(Student::getAge).reversed()).collect(Collectors.toList()).forEach(student -> System.out.println(student.getName()+"-"+student.getAge()));

映射
Map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

运行一下代码观察map与flatmap的区别:

System.out.println("\n---Mapping---");
List<String> stringList = studentList.stream().map(student -> student.getName()).collect(Collectors.toList());
stringList.forEach(s -> System.out.println(s));

stringList结果:{“张三”,”李四”,”王五”,”赵六”}
输出结果:
张三
李四
王五
赵六

List<String[]> stringList3 = studentList.stream().map(student -> student.getName().split("")).collect(Collectors.toList());
stringList3.forEach(s -> System.out.println(s[0]+","+s[1]));

stringList3结果:{{“张“,“三“},{“李“,“四“},{“王“,“五“},{“赵“,“六“}}
输出结果:
张,三
李,四
王,五
赵,六

System.out.println("---FlatMapping---");
List<String> stringList2 = studentList.stream().flatMap(student -> Arrays.stream(student.getName().split(""))).collect(Collectors.toList());
stringList2.forEach(s -> System.out.println(s));

stringList2结果:{“张“,“三“,“李“,“四“,“王“,“五“,“赵“,“六“}
输出结果:







由上可观察出,
map方法可将student集合中按照name查出新的集合,{“张三”,”李四”,”王五”,”赵六”}

而flatmap方法可将student集合中按照name查出新的集合后每个字符分割后形成新的字符集合,{“张“,“三“,“李“,“四“,“王“,“五“,“赵“,“六“}

flatmap相当于将map的数据扁平化,
即{“张三”,”李四”,”王五”,”赵六”}–>flatmap–>{“张“,“三“,“李“,“四“,“王“,“五“,“赵“,“六“}

Peek

相关方法的详细解说可以参考博文:Java8 Stream语法详解:带图详解
https://blog.csdn.net/qq_18416057/article/details/70143475

3、结束操作
在这里插入图片描述
forEach:迭代流中的每个数据
Foreach和常规for循环的差异不涉及到性能,他们仅仅是函数式风格与传统java风格的差别,此外,foreach是terminal操作,它执行后stream的元素就被“消费”掉了,无法对一个stream进行两次terminal运算。
注意:
Foreach不能修改自己包含的本地变量值,也不能用break/return之类的关键字提前结束循环。

Findfirst 返回stream的第一个元素,或者空
这里比较重点的是它的返回值类型:Optional。他可能含有某值,或者不包含,使用他的目的是尽可能避免NullPointerException

Optional类型
通常聚合操作会返回一个Optional类型,Optional表示一个安全的指定结果类型,所谓的安全指的是避免直接调用返回类型的null值而造成空指针异常,调用optional.ifPresent()可以判断返回值是否为空,或者直接调用ifPresent(Consumer<? super T> consumer)在结果部位空时进行消费操作;调用optional.get()获取返回值。

在更复杂的 if (xx != null) 的情况中,使用 Optional 代码的可读性更好,而且它提供的是编译时检查,能极大地降低NPE这种Runtime Exception对程序的影响,或者迫使程序员更早的在编码阶段处理空值问题,而不是留到运行时在发现和调试

Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。还有例如 IntStream.average() 返回 OptionalDouble 等等。

这里参考博文:
https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/

在这里插入图片描述
Collect

在这里插入图片描述
Reduce 把stream元素组合起来,提供一个起始值,然后按照运算规则进行组合
没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。
字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce

// 字符串连接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); 
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); 
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 过滤,字符串连接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
 filter(x -> x.compareTo("Z") > 0).
 reduce("", String::concat);

第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce(),由于可能没有足够的元素,返回的是 Optional,请留意这个区别。

匹配
Stream 有三个 match 方法,从语义上说:

  • allMatch:Stream 中全部元素符合传入的 predicate,返回 true
  • anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
  • noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true

参考博文:
Java Stream API进阶篇:http://www.cnblogs.com/CarpenterLee/p/6550212.html
深入理解Java Stream流水线:http://www.cnblogs.com/CarpenterLee/p/6637118.html
java8Stream原理深度解析:https://www.cnblogs.com/Dorae/p/7779246.html
Java8foreach循环效率:https://www.cnblogs.com/yiwangzhibujian/p/6965114.html
每个方法的使用
https://www.cnblogs.com/andywithu/p/7404101.html
JAVA8新特性[第四季]-强大的Stream API:https://blog.csdn.net/liudongdong0909/article/details/77429875#41-筛选与切片

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值