6.5 Stream 流的转换方法
- Object[] toArray() 返回一个Object数组
- A[] toArray(IntFunction<A[]> generator) 返回一个 指定类型的数组
- collect(Collectors.toList()) 将 Stream 流转换为 list 集合
- collect() 系列方法
- Stream.generate() 创建一个无限流,一般用于生成随机数等方法。需要配合findFirst获取他第一个函数,值为 Optional 。
- Stream.iterate(T,UnaryOperator<T,T>) 无限流,函数通过对第一个参数的操作,生成无线流,需要和 limit 配合使用,限制生成的个数
写几个小例子:
package cn.zxhysy.jdk8.steam;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.Collectors;
public class StreamTest4 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hellow", "world", "helloworld");
// toArray 将stream流转为一个数组,接受一个 IntFunction<R>(整数函数)返回一个R类型
// length 就是数组长度
String[] strings = stream.toArray(length -> {
System.out.println(length); //3
return new String[length];
});
// String[] strings = stream.toArray(String::new)
// 将流转换为集合
List<String> list = stream.collect(Collectors.toList());
Arrays.asList(strings).forEach(System.out::println);
}
}
使用 collect 方法实现转成 ArrayList
R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
这里我们先看下 Collectors.toList() 方法
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
java8 源码中的示例
List<String> asList = stringStream.collect(ArrayList::new, ArrayList::add,ArrayList::addAll);
第一各参数:返回的数据类型
第二个参数:是将每一个对象添加到一个集合里面
第三个参数:是将list集合添加起来,我测试打印数据,但数据没效果,theList1.addAll() 没写也不影响结果, 别人是这么介绍了:第二个参数每次执行都会产生一个新的list,然后把 每个list的数据添加起来。
package cn.zxhysy.jdk8.steam;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamTest4 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hellow", "world", "helloworld");
List<String> list = stream.collect(() -> new ArrayList(),
(theList, item) -> {
System.out.println("-------");
System.out.println("前:theList" + theList);
theList.add(item);
System.out.println("后:theList" + theList);
},
(theList1, theList2) -> {
//这里就算注释了,打印的结果都不变
// 这条语句永远不会输出
// System.out.println("1--------");
// theList1.addAll(theList2);
});
//List<String> list = stream.collect(ArrayList:: new, List::add, List::addAll);
list.forEach(System.out::println);
}
}
Collectors.toCollection(集合类型):
//Collectors.toCollection(这里可以写你想转换的集合类型)
stream.collect(Collectors.toCollection(ArrayList::new));
Collectors.toSet() 返回Set集合
Collectors.joining() 连接字符串 等等
创建流的其他方式:
package cn.zxhysy.jdk8.steam;
import java.util.UUID;
import java.util.stream.Stream;
public class StreamTest6 {
public static void main(String[] args) {
Stream<String> stream = Stream.generate(UUID.randomUUID()::toString);
//stream.forEach(System.out::println);这个无限循环
System.out.println(stream.findFirst().get());
stream.findFirst().ifPresent(System.out::println);
// iterate 生成无限串行流,必须无limit配合使用
Stream.iterate(1, item -> item + 2).limit(6).forEach(System.out::println);
}
}
6.6 Stream 流的常用操作方法
介绍流的操作前,先给出个问题:
1, 3, 5,7, 9,11
找出该流中大于2的元素,然后将每个元素乘以2,然后忽略掉流中的前两个元素,然后再去流中的前两个元素,最后求出流中元素的总和
Stream<Integer> stream = Stream.iterate(1, item -> item + 2).limit(6);
int sum = stream.filter(item -> item > 2)
.mapToInt(item -> item * 2)
.skip(2)
.limit(2)
.sum();
System.out.println(sum);//32
改下需求,最后求的是最小值:
OptionalInt min = stream.filter(item -> item > 2)
.mapToInt(item -> item * 2)
.skip(2)
.limit(2)
.min();
min.ifPresent(System.out::println);//14
可以看到,min() 或者 max() 方法,获取的值都是 Optional 类型,这都是为了防止 NPE 异常,而设计的。为什么sum() 返回的是 int, 这是因为求和时,默认值是0。
再假设,我们要同时获取最大值最小值以及总和,又需要怎么做。
IntSummaryStatistics summaryStatistics = stream.filter(item -> item > 2)
.mapToInt(item -> item * 2)
.skip(2)
.limit(2)
.summaryStatistics();
int min = summaryStatistics.getMin();
System.out.println(min);
返回的 IntSummaryStatistics 中,有对返回流结果的一系列处理,如最大值,最小值、总和等。
上面所用到的方法:
- filter(boolean) 过滤,通过断言(boolean值)判断,值为 false 被过滤
- mapToInt(item) 映射操作,对一个值进行操作,如加减乘除.类似的还有 map、mapToDouble,用 mapToInt 是为了防止 数据自动包装、拆装
- skip(int) 忽略,忽略几个元素,从1开始
- limit(int) 截取,截取几个元素,从1开始
- sum() 求和, 算术求和
- min()、max() 求最小值、最大值,返回 OptionalInt 类型
- summaryStatistics() 统计函数,返回 IntSummaryStatistics 流,里面包含对元素的数值处理方法,如 getMin、getMax 等等。
- forEach 遍历
- sort(compare) 排序,默认自然遍历
- flatMap(类型 -> return stream) 打平 (后面有例子单独介绍*****)
注意:
- 凡是流的中间操作返回的肯定是流,终止操作要么没有返回值,要么返回值是其他非流类型。
- 异常:流已经被操作了,流已经被关闭了。对于 流 其实与 IO 流 是同个道理的,一旦流被操作、被使用了、关闭了,就不能再使用这个流。如何避免,就需要在第一次调用的时候生成新的流,用新的流来操作。建议使用链式编程
- 流的中间操作都是懒加载的,只有终止操作(及早操作)出现,才会执行中间操作,试下下列代码执行结果
- 任何对无限流的操作必须在 limit 之后,不然会导致死循环,看下面第二例子
package cn.zxhysy.jdk8.steam;
import java.util.Arrays;
import java.util.List;
public class StreamTest7 {
public static void main(String[] args) {
List<String> list = Arrays.asList("hello", "world", "hello world");
// 这里的代码,运行起来是不会打印 test 的,
// 正确的说法是map,没有被执行
// 在 stream 中,中间操作是懒加载额
// list.stream().map(item -> {
// String result = item.substring(0, 1).toUpperCase() + item.substring(1);
// System.out.println("test");
// return result;
// });
// 只有调用了 forEach 方法之后才有打印值
list.stream().map(item -> {
String result = item.substring(0, 1).toUpperCase() + item.substring(1);
System.out.println("test");
return result;
}).forEach(System.out::println);
}
}
stream 会将所有的中间操作全部存储起来后遇到终止操作时,再执行。
无限流死循环情况
package cn.zxhysy.jdk8.steam;
import java.util.stream.IntStream;
public class StreamTest8 {
public static void main(String[] args) {
// IntStream.iterate(0, i -> (i+1)%2)
// .distinct().limit(6)
// .forEach(System.out::println);
// 上面的代码回无限循环下去
// 因为 distinct 方法不知道 iterate 会无限循环重复两个值 0 1
// 还不能走到limit 那一步就死循环了。
// 正确的修改
IntStream.iterate(0, i -> (i+1)%2)
.limit(6)
.distinct()
.forEach(System.out::println);
}
}
flatMap 把多个stream合成一个stream,就是把结果打平
下面给出一道去重的题目:
package cn.zxhysy.jdk8.steam;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamTest11 {
public static void main(String[] args) {
List<String> list = Arrays.asList("hello world", "world hello",
"hello world hello", "hello welcome");
// 找出集合中的所有单词,去重
// 下面写法是错的, split 返回的是一个字符串数组所以 distinct 对字符串数组去重就没作用了
// List<String[]> collect = list.stream().map(item -> item.split(" "))
// .distinct().collect(Collectors.toList());
// collect.forEach(item -> Arrays.asList(item).forEach(System.out::println));
// 正确做法
list.stream().map(item -> item.split(" "))
.flatMap(Arrays::stream)
.distinct().collect(Collectors.toList())
.forEach(System.out::println);
}
}
再给出一道题目:
list1 存放这招呼方式,list2 为每个人名,要求输出 给每个人打招呼。
package cn.zxhysy.jdk8.steam;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamTest12 {
public static void main(String[] args) {
List<String> list1 = Arrays.asList("Hi", "Hello", "你好");
List<String> list2 = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
// 给每个人前加招呼
List<String> collect = list1.stream().
flatMap(item -> list2.stream().map(item2 -> item + " " + item2))
.collect(Collectors.toList());
collect.forEach(System.out::println);
// 输出 Hi zhangsan hi lisi....
}
}
6.7 外部迭代和内部迭代
6.7.1 外部迭代
在java8之前,我们都是有 for 或者 增强for等用来遍历数据集合,对数据进行操作。
List<Student> list = new ArrayList<>();
for(int i = 0; i < StudentList.size(); ++i){
// todos
if(xxx){
list.add(student.get(i));
}
}
Collections.sort(list, comparator);
for(Student s: list){
sout(s.getName);
}
大量麻烦操作,需要我们处理,且可读性差。
6.7.2 内部迭代
在java8之后,采用流的方式对数据进行一系列的操作,这里就不做介绍了。
与外部迭代相比,内部迭代采用的是描述性的语音,而不是命令是的操作。不仅简化了操作,还增强了代码的可阅读性。内部迭代采用的数据是源本身,而不会生成多余的数据来比较。内部迭代和外部迭代的主要区别就是操作对象不同,内部迭代操作的是流,外部迭代操作的是集合。
集合和流的区别:集合关注的是数据与数据存储本身,流关注的是对数据所执行的计算