引言:
Stream定义:
A sequence of elements from source that supports aggregate operations.
sequence of elements:一个流对外提供一个接口,可以访问到一串特定的数据。流不存储元素,但是可以根据需要进行计算转化
source:数据来源,如数据结构,数组,文件等
aggregate operation:聚合操作,流支持像SQL操作或者其他函数式语言的操作,如filter、map、reduce、find、match、sorted等
Stream流特点:
pipelining:很多流操作也是返回一个流,如.filter()
Internal Iteration:流操作进行迭代,用户感知不到循环遍历。
流的工作流程:
流的创建
流的转换:将流转换为其他流的中间操作,可包括多个步骤(惰性操作)
流的计算结果:该操作会强制执行之前的惰性操作,也就是说只有第三步启动,才会执行第二部的惰性操作,此步骤之后,流就不可用了
一切流操作都逃不出流的三大工作流程!一个流只能被使用一次
一、stream流的创建——产生一个流对象
1.Collection接口的stream方法
Stream<String> as = new ArrayList<String>().stream();
Stream<String> hs = new HashSet<String>().stream();
//还有LinkedList,linkedSet,TreeSet,Vector等等
2.Arrays.stream可以将数组转化为Stream
Stream<String> stream = Arrays.stream("a,b,c,d,e".split(","), 3, 5);
//数组第3到第五个元素放到流中去
3.利用Stream类进行转化
//of方法,直接将数组转化
Stream<Integer> c1 = Stream.of(new Integer[5]);
Stream<String> c2 = Stream.of("a,b,c".split(","));
Stream<String> c3 = Stream.of("a", "b", "c");
//产生一个空流
Stream<Object> d1 = Stream.empty();
//generate方法,接收一个lambda表达式
Stream<String> e1 = Stream.generate(() -> "hello");
Stream<Double> e2 = Stream.generate(Math::random);
//iterate方法,接受一个种子,和一个lambda表达式
Stream<BigInteger> iterate = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));
4.基本类型流
IntStream、LongStream、DoubleStream(只有这三种)
IntStream s1 = IntStream.of(1, 2, 3, 4, 5);
s1 = Arrays.stream(new int[]{1,2,3});
s1 = IntStream.generate(() -> (int) (Math.random() * 100));
s1=IntStream.range(1,5);//1,2,3,4
s1=IntStream.rangeClosed(1,5);//1,2,3,4,5
IntStream s2 = IntStream.of(1, 2, 3, 4, 5);
Stream<Integer> s3 = s2.boxed();//转换为对象流
IntStream s4 = s3.mapToInt(Integer::intValue);//转换为IntStream
5.并行流——使得所有的中间转换操作都将被并行化
Stream<String> stringStream = new ArrayList<String>().parallelStream();//分治操作
IntStream intStream = IntStream.range(0, 10000000).parallel();
long count = intStream.filter(n -> n % 2 == 0).count();
System.out.println(count);
//需要保证传给并行流的操作不存在竞争
6.其他类/方法产生Stream流
Stream<String> contents = Files.lines(Paths.get("D:\\PATH\\项目报告.docx"));//New IO(NIO)
Stream<String> words = Pattern.compile(",").splitAsStream("a,b,c");//将,编译成正则表达式
二、stream流的转换——从一种流到另外一种流
1.过滤,去重
// 过滤
// filter(Predicate<? super T> Predicate)
// 接受一个lambda表达式,对每个元素进行判定,符合条件的留下
Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = s1.filter(n -> n > 2);
s2.forEach(System.out::println);//3,4,5
//distinct()
Stream<Integer> s1 = Stream.of(1, 1, 2, 3, 3, 4, 4, 5, 6, 7, 7);
Stream<Integer> s2 = s1.distinct();
s2.forEach(System.out::println);//1,2,3,4,5,6,7
对象流去重,先调用hashcode方法,再调用equals方法,和hashset添加元素判断重复方式一样
ArrayList<Student> students = new ArrayList<>();
students.add(new Student("tom",20));
students.add(new Student("tom",20));
students.add(new Student("Jerry",20));
students.add(new Student("Jerry",18));
Stream<Student> s3 = students.stream().distinct();
s3.forEach(n-> System.out.println(n.toString()));
//Student{name='tom', age=20}
//Student{name='Jerry', age=20}
//Student{name='Jerry', age=18}
@Override
public boolean equals(Object o) {
Student s=(Student) o;
if ((this.age==s.age)&&this.name.equals(s.name)){
return true;
}else {
return false;
}
}
@Override
public int hashCode() {
return name.hashCode()*1000+age;
}
2.排序
基本类型包装类排序
//默认排序
Stream<Integer> s1 = Stream.of(3, 2, 1, 4, 6, 5);
Stream<Integer> s2 = s1.sorted();
s2.forEach(System.out::print);//123456
//自定义排序
Stream.of(plannets).sorted(Comparator.comparing(String::length)).forEach(System.out::print);//Mars,Venus,Earth,Saturn,Uranus,Mercury,Jupiter,Neptune
//Stream.of(plannets).sorted((n1,n2)->n1.length()-n2.length()).forEach(System.out::print);
对象排序
ArrayList<Student> students = new ArrayList<>();
students.add(new Student("tom",20));
students.add(new Student("tom",20));
students.add(new Student("Jerry",20));
students.add(new Student("Jerry",18));
Stream<Student> s7 = students.stream().sorted();//默认调用对象的compareTo()方法
s7.forEach(n-> System.out.println(n.toString()));
//Student{name='Jerry', age=18}
//Student{name='tom', age=20}
//Student{name='tom', age=20}
//Student{name='Jerry', age=20}
@Override
public int compareTo(Student o) {
return this.age-o.age;
}
3.转化
1).map()
利用方法引用对流每个元素进行函数计算
Stream<Double> s1 = Stream.of(-1.5, 2.5, -3.5);
Stream<Double> s2 = s1.map(Math::abs);
s2.forEach(System.out::println);//1.5,2.5,3.5
利用lambda表达式对流元素进行函数计算
Stream<Integer> s3 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s4 = s3.map(n -> n * n);
s4.forEach(System.out::println);//1,4,9,16,25
可以对每个元素计算并返回stream流
public static Stream<String> letters(String word){
ArrayList<String> result = new ArrayList<>();
for(int i=0;i<word.length();i++){
result.add(word.substring(i,i+1));
}
return result.stream();
}
Stream<Stream<String>> allLetters = Stream.of(plannets).map(n->letters(n));
allLetters.forEach(n-> {
n.forEach(System.out::print);//MercuryVenusEarthMarsJupiterSaturnUranusNeptune
});
// Stream<Stream<String>> allLetters = Stream.of(plannets).map(n->letters(n));
// allLetters.forEach(n-> {
// n.forEach(System.out::print);//MercuryVenusEarthMarsJupiterSaturnUranusNeptune
// });//Stream流中的元素还是一个Stream流
Stream<String> allLetters = Stream.of(plannets).flatMap(n -> letters(n));//将结果扁平化,流中元素是字符
allLetters.forEach(System.out::println);//一次遍历就可得到,每个String字符
2)抽取、跳过、连接
Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> s2 = s1.limit(3);
s2.forEach(System.out::println);//1,2,3
Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> s2 = s1.skip(8);
s2.forEach(System.out::println);//9,10
//连接两个流
Stream<String> s1 = Stream.concat(letters("hello"), letters("world"));
s1.forEach(System.out::print);//helloworld
4.其他...
Stream<Integer> s1 = Stream.iterate(1, n -> n * 2);
Stream<Integer> s2 = s1.peek(n -> System.out.println("number:" + n)).limit(5);//对流做一些额外操作
s2.forEach(System.out::println);//number:1 1number:2 2number:4 4number:8 8number:16 16
三、流的计算结果——终止操作
在此之前,先介绍一下Optional类型=_=
Integer[] a = {1, 2, 3, 4, 5, 6};
Stream<Integer> s1 = Stream.of(a);
Optional<Integer> maxResult = s1.max((n1, n2) -> n1 - n2);
System.out.println("最大数:"+maxResult.get());//最大数:6
Integer[] a = {1, 2, 3, 4, 5, 6};
Stream<Integer> s1 = Stream.of(a);
Optional<Integer> max = s1.filter(n -> n > 10).max((n1, n2) -> n1 - n2);
System.out.println("最大数:"+max.get());//err:No value present
第二段代码报错没有值匹配是因为optional源码里面有个空值判断
Optional类型是什么呢?
为了解决Java流操作返回空指针异常的问题,在流运算中,避免对象是否为null的判定
封装了Optional<T>
包装器对象
要么包装了类型T的对象,要么没有包装任何对象
如果T是null,那么可以返回一个替代物
Optional<String> s1=Optional.of("abc") ;
String s = s1.get();
System.out.println("s:"+s);//abc
Optional<String> eo = Optional.empty();
String def = eo.orElse("def");//Return the value if present, otherwise return {@code other}.
System.out.println(def);//def
Optional对象的创建和常用方法
Optional<String> s1 = Optional.of("abc");
Optional<String> s2 = Optional.empty();
String s3=null;
Optional<String> s4=Optional.ofNullable(s3);//这里s3为空,等价于Optional.empty();
//get()获取值,不安全
//orElse(T v)获取值,如果值不存在,采用替代
//orElseGet(supplier)获取值,如果值不存在,采用lambda表达式结果替代
//orElseThrow()获取值,如果值不存在,抛出异常
//ifPresent()判空,不空返回true
//isPresent(Consumer)判空,不为空,进行consumer式操作
//map(Function)将值传递给Function函数计算,为空不计算
注意:使用get时,容易引发NoSuchElementException异常;
使用isPresent判空,和判断null一样低效。
1.简单约简(聚合函数):count、max、min/.......
//count()计数
//max(Comparator)最大值,需要比较器
//min(Comparator)最小值,需要比较器
//findFirst()找到第一个元素
//findAny()找到任意一个元素
//anyMatch(Predicate)任意元素满足Predicate,返回true
//allMatch(Predicate)所有元素满足Predicate,返回true
//noneMatch(Predicate)没有任何元素满足predicate,返回true
2.自定义约简:reduce
可以在标准函数库的操作没有的时候来自定义。
Integer[] a = {2, 4, 6, 8, 10};
Stream<Integer> s1 = Stream.of(a);
Optional<Integer> sum = s1.reduce(Integer::sum);//传递一个二元函数
System.out.println(sum.orElse(0));//30
Integer[] a = {2, 4, 6, 8, 10};
Stream<Integer> s1 = Stream.of(a);
Optional<Integer> product = s1.reduce((x, y) -> x * y);//传递一个二元函数BinaryOperator
System.out.println(product.get());//3840
Integer[] a = {2, 4, 6, 8, 10};
Stream<Integer> s1 = Stream.of(a);
Integer product3 = s1.reduce(2, (x, y) -> x * y);//传递一个二元函数BinaryOperator
System.out.println(product3);//7680
3.查看、遍历元素:iterator、forEach
Integer[] a = {2, 4, 6, 8, 10};
Stream<Integer> s1 = Stream.of(a);
Iterator<Integer> it1 = s1.filter(n -> n > 2).iterator();
while(it1.hasNext()){
System.out.println(it1.next());
}
Stream<Integer> s2 = Stream.of(a);
s2.filter(n->n>2).forEach(System.out::println);
4.存放到数据结构中
Integer[] a = {2, 4, 6, 8, 10};
Stream<Integer> s1 = Stream.of(a);
List<Integer> collect = s1.collect(Collectors.toList());
System.out.println(collect);
//toArray(),将结果转为数组
//collect(Collectors.toList())转为List
//collect(Collectors.toSet())转为Set
//collect(Collectors.toMap())转为Map
//collect(Collectors.joining())将结果连接起来
Java Stream的优点
统一转换元素,无需之前那样遍历
过滤元素
单个操作合并元素
将元素序列存放到某一个集合中
.......
类似SQL操作”做什么而非怎么做“
简化了串行,并行的大批量操作
Java Stream 和循环迭代代码的区别
1.Stream广泛使用lambda表达式,只能读取外围的final或者effectively final变量,循环迭代代码可以读取、修改任意的局部变量
2.在循环迭代代码块中,可以随意break、continue、return。或者抛出异常,而lambda表达式无法完成这些事情。
注意事项:
1.一个流,一次只能一个用途,不能多个用途,用了不能再用(否则会流已经被操作或者被关闭报错)
2.避免创建无限流
3.注意转换的顺序
skip().limit()
limit().skip()
4.谨慎使用并行流
底层使用Fork-join Pool,处理计算密集型任务
数据量过小不用
数据结构不容易分解不用,LinkedList等
数据频繁拆箱装箱不用
设计findFirst或者limit不用
5.Stream和Collection可以相互转化
数据可能无限,用Stream
数据很大很大,用Stream
调用者使用查找、过滤、聚合操作,用Stream
调用者使用过程中,发生数据改变,而调用者需要对数据一致性(实时性)有较高要求,用collection