流由3部分构成
1、源
2、零个或多个中间操作(返回新的Stream对象)
3、终止操作(则不会返回Stream对象,甚至可能不返回值)
流操作的分类
1、惰性求值
2、及早求值
例如stream.x().y().z().count()。stream是源,x().y().z()是中间操作,也是惰性求值,即不会立即执行,而count()是终止操作,也是及早求值,会立即执行,而中间操作也会在终止操作执行的时候才执行
Stream接口继承BaseStream接口,BaseStream接口继承AutoCloseable接口,AutoCloseable接口是一个自动关闭资源的接口,里面只有一个close方法,BaseStream也继承该方法,用于关闭流,但是对于一般情况而言,流是不需要关闭的,大多数流都是集合、数组或者生成器函数,但是如果是IO流,那么就需要关闭,AutoCloseable的close方法调用是使用try-with-resources语句形式。
流的执行可以是串行的,也可以是并行的,source.parallelStream()获取并行流,stream.parallel()获取并行流,source.stream()串行流,stream.sequential()串行流,可以获取多次并行流或者串行刘,但是最终只以最后获取的流的类型为准。
流的创建:
使用流本身的方法:
Stream<Integer> stream1 = Stream.of(1);
Stream<String> stream2 = Stream.of(new String[]{"name","age","sex"});
IntStream intStream = IntStream.rangeClosed(2,9);
Collection接口中新增了stream()方法,使用相关集合可以调用此方法创建流
List<String> list = Arrays.asList("1","2","3","4");
Stream<String> stream = list.stream();
流不存储值,通过管道的方式获取值
流的本质是函数式的,对流的操作会生成一个结果,不过并不会修改底层数据源,集合可以作为底层数据源
延迟查找,很多流操作(过滤、映射、排序等)都可以延迟实现。
流的使用:
public class MyTest3 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","changsha");
//采用Lambda表达式将stream转换成字符串数组,toArray方法参数为IntFunction<A[]>函数式接口
//该函数式接口接收一个int参数,然会一个A[]数组,int参数需要转换数组的长度,编译器自动实现将length
//传入apply方法中
String[] arr1 = stream.toArray(length -> new String[length]);
//采用构造方法引用的形式,这里如果是在运行的时候会报错,因为不能对同一个流做不同的操作
String[] arr2 = stream.toArray(String[]::new);
for (String s : arr1){
System.out.println(s);//输出"hello","world","changsha"三个字符串
}
//采用便捷的方式将流转换成集合
List<String> list1 = stream.collect(Collectors.toList());
//效果等同于list1的实现方式,只不过上述实现在底层已经实现了相关的操作;
//这里collect传入3个函数式接口,collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner),第一个
//Supplier的抽象方法是不接收参数返回一个值,这里用于返回最终的list,第二个参数BiConsumer的抽象方法是接收两个参数,不返回值,其作用使将当前流中的每一个
//元素添加到第一个函数式接口返回的list中,第三个参数BiConsumer传递了两个list类型,是将流中每一个元素添加到list中后合并为一个list,并返回
List<String> list2 = stream.collect(() -> new ArrayList<>(),(theList,item) -> theList.add(item),(theList2,theList3) -> theList2.addAll(theList3));
//使用方法引用的方式
List<String> list3 = stream.collect(ArrayList::new,ArrayList::add,ArrayList::addAll);
}
}
Stream.iterate(1,item -> item + 2).limit(10).
filter(item -> item > 2).map(item -> item *2).
skip(2).limit(2).reduce((item1,item2) -> item1 + item2).
ifPresent(System.out::println);
上述代码含义:
iterate(1,item -> item + 2) :以1为初始因子,然后在其基础上+2,是一个无限流,如果不做限制的话
limit(10):针对前面语句做限制,表示取前10个
filter(item -> item > 2):表示过滤流中大于2的元素
map(item -> item *2):表示对流中的元素乘以2
skip(2):表示忽略掉流中的前两个元素
limit(2):表示取流中前2个
reduce((item1,item2) -> item1 + item2):表示对流中的两个元素求和,返回的是一个Optional类型
ifPresent(System.out::println):表示如果Optional中的value不为空,则打印出value的值
整个语句表示:使用流,以1为初始因子,然后递增加2,取前10个元素,然后取里面大于2的元素,然后对其中的元素乘以2,然后忽略掉前2个元素,然后获取前2个元素,并对其求和,然后如果不为空则打印出来
为什么有了Java中提供了通用方法后,比如map方法后还提供mapToInt,mapToLong等方法,是为了减少自动装箱拆箱的操作,提升效率
Stream中sum方法返回的是int,max,min返回Optional,因为如果没有值,那么sum返回的是0,而max 和min返回的空
Stream不能同时操作,如:
Stream<Integer> stream = Stream.of(1);
stream.filter(item -> item >1);
stream.distinct();//这句会报错,因为不能对同一个流做不同的操作
可以修改为:
Stream<Integer> stream = Stream.of(1);
Stream<Integer> stream1 = stream.filter(item -> item >1);
stream1.distinct();
中间操作,惰性求值示例:
public class MyTest6 {
public static void main(String[] args) {
List<String> list = Arrays.asList("hello","world");
//如果是这么写的话,运行程序是不会输出test,因为map是中间操作,惰性求值,如果在后面加上forEach则会打印test
list.stream().map(item -> {
String result = item.substring(0,1).toUpperCase() + item.substring(1);
System.out.println("test");
return result;
});
}
}
与迭代器不同的是,Stream可以并行化操作,迭代器只能命令式地,串行化操作
当使用串行方式遍历时,每个item读完后再读下一个item
使用并行去遍历时,数据会被分成多个段,其中每一个段在不同的线程中处理,然后将结果一起输出
Stream的并行操作依赖于Java7中引入的Fork/Join框架
内部迭代与外部迭代的区别:
流是内部迭代,集合是外部迭代
集合是关注的数据与数据存储本身;
流关注的是对数据执行的计算
流与迭代器类似的一点是:流是无法重复使用或者消费的
并发流与流的短路
并发流示例:
public class MyTest7 {
public static void main(String[] args) {
List<String> uuidList = new ArrayList<>(5000000);
for (int i = 0; i < 5000000; i++){
uuidList.add(UUID.randomUUID().toString());
}
System.out.println("start single sorted !!!!");
long startTime = System.currentTimeMillis();
uuidList.stream().sorted().count();
long endTime = System.currentTimeMillis() - startTime;
System.out.println("end single sorted:"+endTime);
//使用并发流必须是处理无状态的数据,否则会出现问题
System.out.println("start parallel sorted !!!!");
startTime = System.currentTimeMillis();
uuidList.parallelStream().sorted().count();
endTime = System.currentTimeMillis() - startTime;
System.out.println("end parallel sorted:"+endTime);
}
}
流的短路,在日常程序中会使用 语句1 && 语句2 ,或者执行 语句1 || 语句2 ,可以知道如果是&& 操作,那么语句1为假的话,那么语句2就不会执行;执行 ||操作,如果语句1为真,那么语句2不会执行,直接返回真,流的操作也是一样。
public class MyTest8 {
public static void main(String[] args) {
List<String> list = Arrays.asList("hello","world","hello world");
list.stream().mapToInt(item ->{
int length = item.length();
System.out.println(item);
return length;
}).filter(item -> item == 5).findFirst().ifPresent(System.out::println);
}
}
上述示例只会输出hello,5;
流的操作可以看做流本身是一个池子,里面存放了流定义的许多方法,如filter,map,limit等等,而这些方法基本上都是接收函数式接口的参数,具体的实现由用户传入,也就是说流在开始的时候将所有的方法准备好,用户在实际使用流的时候在相应的流操作中传入相应的函数式接口的实现,然后流中的每一个元素执行所有的操作,而不是流中的元素先执行操作1,所有元素执行完操作1之后再执行操作2。上述示例中为什么只输出hello、5,因为流中的每一个元素会先执行所有的操作,如hello元素会先执行mapToInt,直接输出hello,执行完之后执行filter,符合条件,然后执行findFirst,这里一个元素执行完流的操作,后面的ifPresent不属于流的操作。因为流的操作也遵循短路操作,所以当元素hello执行完满足条件,则后面的元素就不会执行了,这里是||的关系,如果将hello改为hello1,使之不满足长度等于5,那么示例中将输出hello1,world,5,以此类推,如果都不满足长度等于5,将会全部打印出来,但是长度就不会打印
flatMap的使用:flatMap可以将多个list产生的流合并为一个list,如下示例表示将list中的元素用“ ”分割,然后去重输出其中的的元素
public class MyTest9 {
public static void main(String[] args) {
List<String> list = Arrays.asList("hello world","hello wellcome","hello nihao world");
//使用lambda表达式
list.stream().flatMap(item -> Arrays.asList(item.split(" ")).stream()).distinct().collect(Collectors.toList()).forEach(System.out::println);
//使用方法引用
list.stream().map(item -> item.split(" ")).flatMap(Arrays::stream).distinct().collect(Collectors.toList()).forEach(System.out::println);
}
}
下面需要将list1中的元素与list2中的元素合并,如hizhangsan,hilisi,hiwangwu,hellozhangsan…
public class MyTest10 {
public static void main(String[] args) {
List<String> list1 = Arrays.asList("hi","hello","你好");
List<String> list2 = Arrays.asList("zhangsan","lisi","wangwu");
//先获取list1中的元素,然后对list2中的每个元素与list1中的元素连接起来
list1.stream().flatMap(item -> list2.stream().map(item2 -> item.concat(item2))).forEach(System.out::println);
}
}
Stream分组(groupingBy)和分区(partitioningBy):比如将5个Student对象按照年龄分组,下面例子分为4组,有两个年龄相同,使用Collectors中的相关方法可以实现,返回的是map,key是选定的分组属性,value是List<Student>。
public class MyTest11 {
public static void main(String[] args) {
Student s1 = new Student("zhangsan",20,100);
Student s2 = new Student("lisi",30,80);
Student s3 = new Student("wangwu",50,10);
Student s4 = new Student("zhangsan",40,70);
Student s5 = new Student("zhangsan",20,60);
List<Student> list = Arrays.asList(s1,s2,s3,s4,s5);
//分组,按照学生年纪分组
Map<Integer,List<Student>> map = list.stream().collect(Collectors.groupingBy(item -> item.getAge()));
//分区,筛选出年纪大于30的学生
Map<Boolean,List<Student>> map1 = list.stream().collect(Collectors.partitioningBy(item -> item.getAge() >30));
System.out.println(map);
}
}
Collectors使用介绍:https://www.cnblogs.com/yuanbing1226/p/9005476.html
// Accumulate names into a List
* List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());
*
* // Accumulate names into a TreeSet
* Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));
*
* // Convert elements to strings and concatenate them, separated by commas
* String joined = things.stream()
* .map(Object::toString)
* .collect(Collectors.joining(", "));
*
* // Compute sum of salaries of employee
* int total = employees.stream()
* .collect(Collectors.summingInt(Employee::getSalary)));
*
* // Group employees by department
* Map<Department, List<Employee>> byDept
* = employees.stream()
* .collect(Collectors.groupingBy(Employee::getDepartment));
*
* // Compute sum of salaries by department
* Map<Department, Integer> totalByDept
* = employees.stream()
* .collect(Collectors.groupingBy(Employee::getDepartment,
* Collectors.summingInt(Employee::getSalary)));
*
* // Partition students into passing and failing
* Map<Boolean, List<Student>> passingFailing =
* students.stream()
* .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));