stream流
概述:
Stream是一个来自数据源的元素队列,Stream并不会存储元素,而是按需计算,数据源流的来源可以是集合和数组,类似于Iterable的集合类。能够以一种声明的方式处理数据源(集合、数组等),它专注于对数据源进行各种高效的聚合操作(aggregate operation)和大批量数据操作 (bulk data operation)。
关键概念:
1.元素 Stream是一个来自数据源的元素队列,Stream本身并不存储元素。
2.数据源(即Stream的来源)包含集合、数组、I/O channel、generator(发生器)等。
3.聚合操作 类似SQL中的filter、map、find、match、sorted等操作
4.管道运算 Stream在Pipeline中运算后返回Stream对象本身,这样多个操作串联成一个Pipeline,并形成fluent风格的代码。这种方式可以优化操作,如延迟执行(laziness)和短路( short-circuiting)。
5.内部迭代 不同于java8以前对集合的遍历方式(外部迭代),Stream API采用访问者模式(Visitor)实现了内部迭代。
6.并行运算 Stream API支持串行(stream() )或并行(parallelStream() )的两种操作方式。
Stream操作有两个基础的特征:
1.Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
2.内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
两种实现流方式:
1.常用方式(返回一个顺序流)
List<String> list = Arrays.asList("test", "hello", "hello world", "fright");
Stream<String> stream = list.stream();
2.多线程(并行流),必需考虑线程安全问题
List<Employee> list = new ArrayList<>();
//普通流
Stream<Employee> stream = list.stream();
//并行流
Stream<Employee> parallelStream = list.parallelSteam();
两种函数式创建流的方式:
1.迭代
Stream<Integer> stream = Stream.iterate(0, (x) -> x + 2).limit(2);
stream.forEach(System.out::println);
2.生成(创建无限流)
Stream<Double> generate = Stream.generate(new Supplier<Double>(){//函数接口生产者接口
@Override
public Double get(){
return java.lang.Math.random();
}
});
//generate实现了生产者函数接口,所以可以使用lambda
Stream<Double> generateB = Stream.generate(() -> java.lang.Math.random());
Stream<Double> generateC = Stream.generate(java.lang.Math::random).limit(4);
generateC.forEach((System.out::println));
Api测试:
1.filter过滤: stream.filter(s -> s.isEmpty()).forEach(System.out::println)
参数:filter(参数 -> 判断式(返回为Boolean))
@Test
public void testFilter(){
List<String> strings = Arrays.asList("test", "hello", "Hello", "HelloWorld", "小学鸡加油");
//过滤出hello的字符串并输出
strings.stream().filter(s -> s.equals("hello")).forEach(System.out::println);
}
2.concat合并两个流
若两个输入的Stream都是排序的,则新Stream也是排序的;
若输入的Stream中任何一个是并行的,则新的Stream也是并行的;
若关闭新的Stream时,则两个输入的Stream都将执行关闭处理。
@Test
public void testConcat(){
Stream<Integer> s1 = Stream.of(1, 2);
Stream<Integer> s2 = Stream.of(2, 4, 8)
Stream.concat(s1, s2).forEach(num -> System.out.print(num + " "));
}
3.映射map(实际操作流内的元素并可以返回)
该方法接收一个Function函数式接口作为参数,可以将当前流中的T类型转换为另一种R类型。
方法 | 描述 |
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
@Test
public void testMap(){
Stream<String> mapTarget = Stream.of("11", "22", "9");
Stream<Integer> mapRes = mapTarget.map((s -> Integer.parseInt(s)));
mapRes.forEach(s -> System.out.print(s + " "));
}
4.flatMap
flatmap()是将原Stream的元素取代为转换的Stream。
@Test
public void testFlatmap(){
//该Stream会被后面那个处理过的Stream替换
Stream.of(1, 2, 3).flatMap(num -> Stream.of(num * 10)).forEach(System.out::println);
//输出结果:10 20 30
}
5.reduce计算流的元素(重点)
reduce方法有三个重载的方法。
1.使用lambda表达式实现计算操作
@Test
public void testFirst(){
List<Integer> numList = Arrays.asList(1,2,3,4,5);
int result = numList.stream().reduce((a,b) -> a + b ).get();
System.out.println(result);
}
2.该方法首次执行时表达式第一次参数并不是stream的第一个元素,而是通过签名的第一个参数identity来指定。
用处:需要在不改变原集合时增加条件
@Test
public void testSecond(){
List<Integer> numList = Arrays.asList(1,2,3,4,5);
//计算过程: 2 + 1 + 2 ... + 5
int result = numList.stream().reduce(2,(a,b) -> a + b );
System.out.println(result);
}
3.前两种实现有一个缺陷,它们的计算结果必须和stream中的元素类型相同,因此加入第三个签名
方法原型:<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
用处:但返回的结果与原来集合的结果类型不同时,可以使用参数指定返回结果类型
@Test
public void testThrid(){
List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5, 6);
List<String> result = numList.stream().reduce(new ArrayList<String>(), (a, b) -> {
a.add("int转成string:" + Integer.toString(b));
return a;
}, (a, b) -> null);
System.out.println(result);
}
4.参数解析:
使用了parallelStream reduce操作是并发进行的,为了避免竞争,每个reduce线程都会有独立的result combiner的作用在于合并每个线程的result得到最终结果。
BinaryOperator是供多线程使用的,如果不在Stream中声明使用多线程,就不会使用子任务,自然也不会调用到该方法。另外多线程下使用BinaryOperator的时候是需要考虑线程安全的问题。
6.peek消费Stream里面的元素(只对元素进行操作,对结果没有改变)
生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer的实例),新Stream每个元素被消费的时候都会优先执行给定的消费函数
用处:主要用于Debug的时候看到对应消费的元素
@Test
public void testPeekWork(){
Stream.of("one", "two", "three","four")
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());
}
结果: ONE TWO THREE FOUR
@Test
public void testPeekUnwork(){
Stream.of("one", "two", "three","four")
.peek(e -> e.toUpperCase()).collect(Collectors.toList());
}
结果: one two three four
但对对象进行的操作会进行保存(重点)
@Test
public void testPeekObject(){
List<User> userList=Stream.of(new User("a"),new User("b"),new User("c")).peek(u->u.setName("kkk")).collect(Collectors.toList());
userList.forEach(s -> System.out.println(s.getName()));
}
结果: kkk kkk kkk
7.count统计个数
count是统计流中的元素个数
@Test
public void testCount(){
Stream<String> stream = Stream.of("张三", "张无忌", "李四", "王五");
//统计张姓的个数,,count函数使用后会关闭流对象(即后面不能再调用stream)
long sum = stream.filter(s -> s.startsWith("张")).count();
System.out.println(sum);
}
8.limit对流的元素进行截取
limit可以对流进行截取,支取前几个,参数是一个long类型,如果集合当前长度大于参数,则进行截取,否则不进行操作。
@Test
public void testLimit(){
Stream<Integer> stream = Stream.of(1, 23, 43, 142);
stream.limit(3).forEach(num -> System.out.println(num));
}
9.skip跳过流的前几个元素
希望跳过前几个元素,则可以使用skip方法获取一个截取之后的新流。
参数是一个long类型,如果流的当前长度大于n,则跳过前n个,否则,将会得到一个长度为0的空流。
@Test
public void testSkip(){
Stream<Integer> stream = Stream.of(1, 23, 43, 142);
stream.skip(2).forEach(num -> System.out.println(num));
}
10.sorted对流的元素进行排序
默认是从小到大排序,可以使用函数进行自定义排序
@Test
public void testSorted(){
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().sorted().forEach(System.out::println);
}
结果: 2 2 3 3 3 5 7
@Test
public void testSortedFunction(){
List<Integer> list = Arrays.asList(2, 3, 1, 5, 9);
//函数的方式
list.stream().sorted((a, b) -> a - b).forEach(System.out::println);
}
结果: 1 2 3 5 9
11.distinct对流内元素进行去重
@Test
public void testDistinct(){
List<Integer> numbers = Arrays.asList(3, 3, 7, 3, 5);
numbers.stream().distinct().forEach(System.out::println);
}
结果: 3 7 5
12.结合使用
@Test
public void testDemo(){
List<String> strings = Arrays.asList("", "lucas", "Hello", "HelloWorld", "武汉加油");
Stream<Integer> stream = strings.stream()
.filter(string -> string.length() <= 6) //过滤掉了"HelloWorld"
.map(String::length) //将Stream从原来元数据的String类型变成了int
.sorted() //int从小到大
.limit(2) //只选前两个
.distinct(); //去重
stream.forEach( System.out::println);
}
Stream的终止操作
最终操作会消耗流,产生一个最终结果。也就是说,在最终操作之后,不能再次使用流,也不能在使用任何中间操作,否则将抛出异常。
方法 | 描述 |
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部 迭代——它帮你把迭代做了) |
Collect方法的使用
collect是一个归约操作,可以接受各种做法作为参数,将流中的元素累积成一个汇总结果。
方法 | 返回类型 | 作用 | 实例 |
---|---|---|---|
toList | List | 把流中元素收集到List | List emps= list.stream().collect(Collectors.toList()); |
toSet | Set | 把流中元素收集到Set | Set emps= list.stream().collect(Collectors.toSet()); |
toCollection | Collection | 把流中元素收集到创建的集合 | Collectionemps=list.stream().collect(Collectors.toCollection(ArrayList::new)); |
counting | Long | 计算流中元素的个数 | long count = list.stream().collect(Collectors.counting()); |
summingInt | Integer | 对流中元素的整数属性求和 | inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary)); |
joining | String | 连接流中每个字符串 | String str= list.stream().map(Employee::getName).collect(Collectors.joining()); |
groupingBy | Map<K, List> | 根据某属性值对流分组,属 性为K,结果为V | Map<Emp.Status, List> map= list.stream() .collect(Collectors.groupingBy(Employee::getStatus)); |
partitioningBy | Map<Boolean, List> | 根据true或false进行分区 | Map<Boolean,List>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage)); |
reducing | 归约产生的类型 | 从一个作为累加器的初始值 开始,利用BinaryOperator与 流中元素逐个结合,从而归约成单个值 | int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum)); |
averagingInt | Double | 计算流中元素Integer属性的平均值 | double avg= list.stream().collect(Collectors.averagingInt(Employee::getSalary)); |
maxBy | Optional | 根据比较器选择最大值 | Optional max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary))); |
minBy | Optional | 根据比较器选择最小值 | Optional min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary))); |
summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值。 如:平均值 | Int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary)); |
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结 果转换函数 | int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); |
参加链接:Java8新特性之Stream流详细总结