Stream 流(重要)
流由三部分构成
- 源(要操作的数据)
- 零个或多个中间操作(每次操作都会产生一个新的流)
- 终止操作(得到结果)
中间操作都会返回一个Stream流,例如Stream,Stream
终止操作不会返回Stream流,有可能不返回值,也有可能返回其他类型的单个值
流操作的分类:
- 惰性求值
- 及早求值
steam.xxx().yyy().zzz().sum();
xxx().yyy().zzz() 就叫做惰性求值如果没有终止操作将不会执行,也叫做中间操作
.sum()就叫做及早求值,直接把值求出来 也叫终止操作
流的三种创建方法
/**
* 流的三种创建方式
*/
public class StreamTest {
public static void main(String[] args) {
//第一种通过静态方法创建
Stream system = Stream.of("hello", "world", "nihao");
//第二种通过数组的方式创建
String[] strings = new String[]{"hello", "world", "nihao"};
Stream system1 = Stream.of(strings);
Stream<String> stream = Arrays.stream(strings);
//第三种(最常见的)集合方式
List<String> list = Arrays.asList("hello", "world", "nihao");
Stream<String> stream1 = list.stream();
}
}
流带来哪些简化
public class StreamTest2 {
public static void main(String[] args) {
IntStream.of(new int[]{5, 6, 7}).forEach(x -> System.out.println(x));
System.out.println("=========");
//range 获取3到8 不包含8
IntStream.range(3, 8).forEach(System.out::println);
System.out.println("=========");
//range 获取3到8 包含8
IntStream.rangeClosed(3, 8).forEach(System.out::println);
/*
输出集合所有整数的平方的和
*/
System.out.println("=========");
List<Integer> list = Arrays.asList(9, 8, 89);
/*
list->源,map(x -> x * x)->中间操作,惰性求值,reduce终止操作,及早求值
*/
System.out.println(list.stream().map(x -> x * x).reduce(0, Integer::sum));
}
}
R collect(Supplier supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);方法解析
Supplier supplier: 创建新结果容器的函数(返回结果)。对于并行执行,此函数可能被多次调用,每次都必须返回一个新值。
BiConsumer<R, ? super T> accumulator, 累加器函数 将流中元素经过操作转换成返回结果中的函数类型(每次合并产生一个结果)
BiConsumer<R, R> combiner) 用于组合两个值(精简结果)的函数,该函数必须与累加器函数兼容类型相同 (就是将每次产生的结果给汇总到到一起 返回)
public class StreamTest4 {
public static void main(String[] args) {
// Stream<String> stringStream = Stream.of("hello", "world", "nihaoa");
//
// String[] strings = stringStream.toArray(String[]::new);
// Arrays.asList(strings).forEach(System.out::println);
// System.out.println("===========================");
/*
将一个流转换成List
*/
Stream<String> stream = Stream.of("hello", "world", "nihaoa");
stream.collect(Collectors.toList()).forEach(System.out::println);
/*
() -> new ArrayList(), 返回的 List集合
(theList, item) -> theList.add(item) 将流中数据转成要返回的集合类型(转换一次生成一个集合)
(theList1, theList2) -> theList1.addAll(theList2) 将返回的每个集合合并到一个集合中返回
就是Collectors.toList() 的封装
*/
stream.collect(() -> new ArrayList(), (theList, item) -> theList.add(item),
(theList1, theList2) -> theList1.addAll(theList2)).forEach(System.out::println);
/*
通过类::实例方法 和构造方法引用代替
*/
List<String> list = stream.collect(LinkedList::new, LinkedList::add, LinkedList::addAll);
System.out.println("=========================");
/*
将字符串拼接
*/
StringBuffer collect1 = stream.collect(StringBuffer::new, StringBuffer::append, StringBuffer::append);
System.out.println(collect1);
}
}
// Stream<String> stream = Stream.of("hello", "world", "nihaoa");
// //Collectors.toCollection 是一种通用的方法
// List<String> list = stream.collect(Collectors.toCollection(LinkedList::new));
// list.forEach(System.out::println);
// System.out.println("=============");
// Stream<String> stream = Stream.of("hello", "world", "nihaoa");
// Set<String> set = stream.collect(Collectors.toCollection(TreeSet::new));
// set.forEach(System.out::println);
Stream<String> stream = Stream.of("hello", "world", "nihaoa");
/*
Collectors.joining() 将流元素按照传过来的顺序进行拼接成字符串
*/
String str = stream.collect(Collectors.joining()).toString();
System.out.println(str);
Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
改接口可以将数据中相同类型的的数据映射成流且合并到一起
public class StreamTest5 {
public static void main(String[] args) {
List<String> list = Arrays.asList("hello", "world", "nihao", "shanghai");
list.stream().map(String::toUpperCase).forEach(s -> System.out.println(s));
System.out.println("=================");
List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5, 6);
list1.stream().map(x -> x * x).collect(Collectors.toList()).forEach(System.out::println);
System.out.println("=================");
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1), Arrays.asList(2), Arrays.asList(3, 4, 5));
/*
map和flatMap 不同 flatMap 的apply方法可以返回流 map的apply只能返回同类型数据
flatMap可以将同类型数据合并 例如集合数据 合并到一起 类似addAll
*/
Stream<Integer> integerStream = stream.flatMap(x -> x.stream().map(y -> y * y));
//与上面相同
// Stream<Integer> integerStream = stream.flatMap(x -> x.stream()).map(y -> y * y);
integerStream.forEach(System.out::println);
}
}
public static Stream iterate(final T seed, final UnaryOperator f) {}
doc 类似迭代器
返回通过将函数f迭代应用于初始元素seed而生成的无限序列串行流(就是无限执行UnaryOperator行为操作一直执行下去),生成由seed、f(seed)、f(f(seed))等组成的流。
流中的第一个元素(位置0)将是提供的种子。对于n>0,位置n处的元素将是对位置n-1处的元素应用函数f的结果。
实例:流的使用方法,以及对应注意事项
public class StreamTest6 {
public static void main(String[] args) {
// Stream<String> stringStream = Stream.generate(UUID.randomUUID()::toString);
//查找流中第一个元素返回Optional 类型 通过ifPresent方法获取到数据
// System.out.println(stringStream.findFirst().get());//未isPresent判断是否为空 如果为空会报错 一般get和isPresent连用
//正确编写
// stringStream.findFirst().ifPresent(System.out::println);
// Stream.iterate(1, x -> x + 8).forEach(System.out::println);//无限执行下去
// Stream.iterate(1, x -> x + 8).limit(6).forEach(System.out::println);//limit限制流的长度 limit一般和iterate连用
System.out.println("====");
/*
找出该流中大于2(过滤)的元素,并将每个元素乘以2(映射),然后忽略流中的前两个元素(skip),
然后取出流中的前两个元素,最后求出流中元素综合
*/
Stream<Integer> integerStream = Stream.iterate(1, x -> x + 8).limit(6);
// System.out.println(integerStream.filter(x -> x > 2).mapToInt(x -> x * 2).skip(2).limit(2).sum());
// integerStream.filter(x -> x > 2).mapToInt(x -> x * 2).skip(2).limit(2).min().ifPresent(System.out::println);
/*
同时调用最大值最小值 总和
summaryStatistics 终止操作 里面统计了 一些求值操作
*/
// IntSummaryStatistics is = integerStream.filter(x -> x > 2).mapToInt(x -> x * 2).skip(2).limit(2).summaryStatistics();
// System.out.println(is.getMax());
// System.out.println(is.getMin());
// System.out.println(is.getSum());
System.out.println("====");
/*
当流已经被使用或已经关闭时 将无法使用
*/
System.out.println(integerStream);
System.out.println(integerStream.filter(x -> x > 2));
System.out.println(integerStream.distinct());
/*
我们可以调用新生成的流
*/
System.out.println(integerStream);
Stream<Integer> integerStream1 = integerStream.filter(x -> x > 2);
System.out.println(integerStream.filter(x -> x > 2));
System.out.println(integerStream1.distinct());
}
}
例:关于中间操作和终止操作
public class StreamTest7 {
public static void main(String[] args) {
List<String> list = Arrays.asList("hello", "world", "nihaoa");
// list.stream().map(x -> x.substring(0,1).toUpperCase() + x.substring(1)).forEach(System.out::println);
System.out.println("============");
/*
惰性求值,如果没有终止操作(即时求值) 那么中间操作流将不会执行
*/
// list.stream().map(x -> {
// String result = x.substring(0, 1).toUpperCase() + x.substring(1);
// System.out.println("test");//未被执行
// return result;
// });
System.out.println("============");
/*
执行了终止操作
*/
list.stream().map(x -> {
String result = x.substring(0, 1).toUpperCase() + x.substring(1);
System.out.println("test");//未被执行
return result;
}).forEach(System.out::println);
}
/*
test
Hello
test
World
test
Nihaoa
*/
}
例
public class StreamTest8 {
public static void main(String[] args) {
Stream.iterate(0, i -> (i + 1) % 2).distinct().limit(6).forEach(System.out::println);//卡在去重
}
}
内部迭代和外部迭代解析
内部迭代和外部迭代
- 内部迭代
上述操作只执行了一个循环通过前面学习forEach执行循环去遍历每个元素,每个元素去执行filter对应的操作 (类似语文填空题 空的是行为操作)
- 外部迭代 (传统的循环,迭代器)
外部迭代则执行多次循环(相当于写作文)
内部迭代和外部迭代的区别
内部迭代和外部迭代的区别:例如传统迭代我们是拿着集合本身的数据 去编写独立的代码取操作对应数据 代码和数据是分开的,而内部迭代是我们拿到集合的流,我们去编写对应的行为操作当流遇到终止操作时会将我们自己编写的代码和流本身自带的数据方法一同执行
总结
集合关注数据和数据存储本身,流关注的是对数据的一种计算(填写行为)
流与迭代器相似的地方:流是无法被重复消费和使用的
串行流和并行流
public class StreamTest9 {
public static void main(String[] args) {
List<String> list = new ArrayList<>(5000000);
for (int i = 0; i < 5000000; ++i) {
list.add(UUID.randomUUID().toString());
}
System.out.println("开始排序");
long time = System.nanoTime();
//stream串行流 单线程执行
// list.stream().sorted().count(); //3716 3.7s
//parallelStream并行流 功能类似stream //多线程执行
list.parallelStream().sorted().count();//1207 1.2s
long time2 = System.nanoTime();
long l = TimeUnit.NANOSECONDS.toMillis(time2 - time);
System.out.println("排序耗时" + l);
}
}
断路操作
public class StreamTest10 {
public static void main(String[] args) {
/*
把长度为5的第一个单词打出来
*/
List<String> list = Arrays.asList("hello", "world", "hello world");
// list.stream().filter(x -> x.length() == 5).findFirst().ifPresent(System.out::println);//hello
/*
当遇到终止操作会将得到的最终流再去执行 自己编写的行为(执行自己的填空语句)
断路操作: 例如当程序执行到.findFirst() 时发现第一个元素满足
(第一个长度为5的单词)时就不会去判断后面元素直接返回
*/
list.stream().mapToInt(x -> {
int length = x.length();
System.out.println(x);
return length;
}).filter(y -> y == 5).findFirst().ifPresent(System.out::println);
}
}
// && || 断路操作
flatMap和Map 区别
public class StreamTest11 {
public static void main(String[] args) {
/*
找出所有的单词 并去重
*/
List<String> list = Arrays.asList("hello welcome", "world hello",
"hello world hello", "hello welcome");
// List<String[]> collect = list.stream().map(x -> x.split(" ")).distinct().collect(Collectors.toList());
// collect.forEach(x -> Arrays.asList(x).forEach(System.out::println));
/*
flatMap 需要返回流
*/
list.stream().flatMap(x -> Arrays.asList(x.split(" ")).stream()).distinct().forEach(System.out::println);
}
}
例:每条打招呼语句对应每个人
public class StreamTest12 {
/*
每条打招呼语句对应每个人
*/
public static void main(String[] args) {
List<String> list = Arrays.asList("Hi", "Hello", "你好");
List<String> l2 = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
list.stream().flatMap(x -> l2.stream().map(y -> x + y)).forEach(System.out::println);
}
}
Stream的分组分区
传统写法:
Stream流写法
例:分组实例
public class StreamTest13 {
public static void main(String[] args) {
Student student = new Student("zhangsan", 100, 20);
Student student2 = new Student("lisi", 90, 20);
Student student3 = new Student("wangwu", 90, 30);
Student student4 = new Student("liuliu", 70, 40);
List<Student> list = Arrays.asList(student, student2, student3, student4);
/*
select * from student group by name 实现根据名字
Collectors.groupingBy 根据返回的名字进行分组
*/
// Map<String, List<Student>> collect = list.stream().collect(Collectors.groupingBy(Student::getUsername));
// System.out.println(collect);
System.out.println("=======================");
/*
select name,count(*) from student group by name
根据名字和数量分组
*/
// Map<String, Long> collect = list.stream().collect(Collectors.groupingBy(Student::getUsername, Collectors.counting()));
// System.out.println(collect);
//根据名字得到平均数
Map<String, Double> collect = list.stream().collect(Collectors.groupingBy(Student::getUsername, Collectors.averagingDouble(Student::getAge)));
System.out.println(collect);
}
}
分区示例
/*
分区 :分两种 一种false (不符合)和true(符合)
{false=[Student{username='liuliu', score=70, age=40}],
true=[Student{username='zhangsan', score=100, age=20}, Student{username='lisi', score=90, age=20}, Student{username='wangwu', score=90, age=30}]}
*/
Map<Boolean, List<Student>> collect = list.stream().collect(Collectors.partitioningBy(x -> x.getScore() >= 90));
System.out.println(collect);
collect.get(false);//获取对应false分区