Java 8引入了全新的Stream API,此Stream与Java I/O包里的InputStream和OutputStream是完全不同的概念,它不同于StAX对XML解析的Stream,也不同于Amazon Kinesis对大数据实时处理的Stream。Stream API更像具有Iterable的集合类,但行为和集合类又有所不同,它是对集合对象功能的增强,专注于对集合对象进行各种非常便捷、高效的聚合操作或大批量数据操作。
Java8主要是在原来面向对象的基础上增加了函数式编程的能力。这样就出现了在Java中使用Lambda表达式,将一个函数作为方法的参数来进行传递。Java8的Stream就是典型的例子,Stream API可以极大提高Java程序员的生产力。
1. 流的常用创建方法
1.1 使用Collection下的 stream() 和 parallelStream() 方法
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
1.2 使用Arrays 中的 stream() 方法,将数组转成流
Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums);
1.3 使用Stream中的静态方法:of()、iterate()、generate()
Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6);
stream2.forEach(System.out::println); // 0 2 4 6 8 10
//Stream接口有两个用来创建无限Stream的静态方法。generate()方法接受一个参数函数,可以使用类似如下代码来创建一个你需要的Stream。
Stream<Double> stream3 = Stream.generate(Math::random).limit(2);
stream3.forEach(System.out::println);
1.4 使用 BufferedReader.lines() 方法,将每行内容转成流
BufferedReader reader = new BufferedReader(new FileReader("F:\\test_stream.txt"));
Stream<String> lineStream = reader.lines();
lineStream.forEach(System.out::println);
1.5 使用 Pattern.splitAsStream() 方法,将字符串分隔成流
Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);
2. 流的中间操作
2.1 筛选与切片
filter:过滤流中的某些元素
limit(n):获取n个元素
skip(n):跳过n元素,配合limit(n)可实现分页
distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素
Stream<Integer> stream = Stream.of(6, 4, 6, 7, 3, 9, 8, 10, 12, 14, 14);
Stream<Integer> newStream = stream.filter(s -> s > 5) //6 6 7 9 8 10 12 14 14
.distinct() //6 7 9 8 10 12 14
.skip(2) //9 8 10 12 14
.limit(2); //9 8
newStream.forEach(System.out::println);
2.2 映射
map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
List<String> list = Arrays.asList("a,b,c", "1,2,3");
//将每个元素转成一个新的且不带逗号的元素
Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", ""));
s1.forEach(System.out::println); // abc 123
Stream<String> s3 = list.stream().flatMap(s -> {
//将每个元素转换成一个stream
String[] split = s.split(",");
Stream<String> s2 = Arrays.stream(split);
return s2;
});
s3.forEach(System.out::println); // a b c 1 2 3
3.流的转换
3.1filter方法
从名字上就能看出来,这是一个Stream的过滤转换,此方法会生成一个新的流,其中包含符合某个特定条件的所有元素。
List<Integer> integerList = Lists.newArrayList();
integerList.add(15);
integerList.add(32);
integerList.add(5);
integerList.add(232);
integerList.add(56);
List<Integer> after = integerList.stream()
.filter(i->i>50)
.collect(Collectors.toList());
System.out.println(after);
运行结果:
[232, 56]
3.2map方法
map方法指对一个流中的值进行某种形式的转换。需要传递给它一个转换的函数作为参数。
List<Integer> integerList = Lists.newArrayList();
integerList.add(15);
integerList.add(32);
integerList.add(5);
integerList.add(232);
integerList.add(56);
//将Integer类型转换成String类型
List<String> afterString = integerList.stream()
.map(i->String.valueOf(i)).collect(Collectors.toList());
3.3flatMap方法
上面用map方法进行流转换的时候,是对每个元素应用一个函数,并将返回的值收集到一个新的流中。但是如果有一个函数,它返回的不是一个值,而是一个包含多个值的流。但是你需要的是一个包含多个流中的元素的集合。
List<Integer> oneList = Lists.newArrayList(),
twoList = Lists.newArrayList();
oneList.add(34);
oneList.add(23);
oneList.add(87);
twoList.add(29);
twoList.add(48);
twoList.add(92);
Map<String,List<Integer>> testMap = Maps.newHashMap();
testMap.put("1",oneList);
testMap.put("2",twoList);
//返回的是一个流的集合,但是我需要的是List<Integer>这样一个集合
List<Stream<Integer>> testList = testMap.values().stream()
.map(number->number.stream()).collect(Collectors.toList());
这个时候就应该使用flatMap将多个流进行合并,然后再收集到一个集合中。
List<Integer> testList = testMap.values().stream()
.flatMap(number->number.stream()).collect(Collectors.toList());
3.4limit方法和skip方法
limit(n)方法会返回一个包含n个元素的新的流(若总长小于n则返回原始流)。
List<Integer> myList = Lists.newArrayList();
myList.add(1);
myList.add(2);
myList.add(3);
myList.add(4);
myList.add(5);
myList.add(6);
List<Integer> afterLimit = myList.stream().limit(4).collect(Collectors.toList());
System.out.println("afterLimit:"+afterLimit);
skip(n)方法正好相反,它会丢弃掉前面的n个元素。
List<Integer> afterSkip = myList.stream().skip(4).collect(Collectors.toList());
System.out.println("afterSkip:"+afterSkip);
用limit和skip方法一起使用就可以实现日常的分页功能:
List<Integer> pageList = myList.stream()
.skip(pageNumber*pageSize)
.limit(pageSize).collect(Collectors.toList());
3.5distinct方法和sorted方法
上面介绍的流的转换方法都是无状态的。即从一个已经转换的流中取某个元素时,结果并不依赖于之前的元素。除此之外还有两个方法在转换流时是需要依赖于之前流中的元素的。一个是distinct方法一个是sorted方法。
distinct方法会根据原始流中的元素返回一个具有相同顺序、去除了重复元素的流,这个操作显然是需要记住之前读取的元素。
List<Integer> myTestList = Lists.newArrayList();
myTestList.add(10);
myTestList.add(39);
myTestList.add(10);
myTestList.add(78);
myTestList.add(10);
List<Integer> distinctList = myTestList.stream()
.distinct().collect(Collectors.toList());
System.out.println("distinctList:"+distinctList);
sorted方法是需要遍历整个流的,并在产生任何元素之前对它进行排序。因为有可能排序后集合的第一个元素会在未排序集合的最后一位。
List<Integer> myTestList = Lists.newArrayList();
myTestList.add(39);
myTestList.add(78);
myTestList.add(10);
myTestList.add(22);
myTestList.add(56);
List<Integer> sortList = myTestList.stream()
.sorted(Integer::compareTo).collect(Collectors.toList());
System.out.println("sortList:"+sortList);
4并行流
Stream的建立,使得并行计算变得容易,但是并行流在使用的时候也是需要注意的。
首先,必须是一个并行流,只要在终止方法执行时,流处于并行模式,那么所有的流操作就都会并行执行。
Stream.of(roomList).parallel();
parallel方法可以将任意的串行流转换为一个并行流。
其次要确保传递给并行流操作的函数是线程安全的。
int[] words = new int[23];
Stream.of(roomList).parallel().forEach(s->{
if(s.size()<10){
words[s.size()]++;
}
});
上面这个例子中的代码就是错误的,传递给并行流的操作并不是线程安全的。可以改为AtomicInteger的对象数组来作为计数器。
我们使在处理集合数据量较大的时候才能体现出并行流的优势,并且目的是为了在保证线程安全的情况下,提升效率,利用多核CPU的资源。
小扩展
使用Stream的API时,在遍历或处理流的过程中当引用外部变量的时候会默认的将变量当成fianl变量来处理。所以有些同学就会觉得在遍历的过程中取不出来集合的索引。其实可以换一种思想可以只遍历集合索引,然后在遍历中取值。
IntStream.range(0,roomList.size()).forEach(i->{
System.out.println(roomList.get(i));
});