Stream是 Java 8新增加的类,用来补充集合类。
Stream代表数据流,流中的数据元素的数量可能是有限的,也可能是无限的。
不存储数据
:流是基于数据源的对象,本身不存储数据元素,而是通过通道将数据传递给操作。函数式编程
:流的操作不会改变数据源。延迟操作
:流的很多中间操作都是延迟执行的,中间操作仅是用于记录处理过程,真正的执行是在terminal操作
中。无限流(无限数据源)
:stream流中的数据不是一次性加载到内存中,而是在用的时候生成,由此可以实现无限流。对于无限流可以通过短路操作在有限时间内完成执行。纯消费
: Stream
不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。
Stream特性
:
如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
而和迭代器又不同的是,Stream
可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item
读完后再读下一个item
。
使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream
的并行操作依赖于 Java7
中引入的 Fork/Join
框架(JSR166y)来拆分任务和加速处理过程。
lambda表达式
lambda
表达式为java带来了闭包的概念。java中lambda不能单独出现,它需要一个函数式接口盛放,lambda表达式的方法体就是函数接口的实现。 函数式接口是指接口中只有一个方法的接口,用注解@FunctionalInterface进行声明。
lambda语法
方法引用:
lambda表达式的一种简写,所引用的方法就是labmbda表达式的方法体实现,左边是容器(可以是类名,实例名),中间是’::’,右边是对应的方法名。
ObjectReference::methodName
一般的引用格式是:
1,如果是静态方法,则ClassName::methodName
,如Object::equals
。
2,如果是实例方法,则InstanceName::methodName
,如
Object obj=new Object();
obj::equals;
3,如果是构造函数,则是ClassName::new
。
案例:
List list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Stream stream = list.stream();
// Stream stream = list.parallelStream();
stream.forEach(System.out::println);
案例 :
Stream stream = Arrays.stream(new String[] { "a", "b", "c" });
stream.forEach(a -> {
System.out.println(a);
});
(1) 通过Stream.of(Object[])
Stream stream = Stream.of("a", "b", "c");
stream.forEach(System.out::print);
(2) 通过IntStream().range(int,int)
IntStream intStream = IntStream.range(0, 10);
intStream.forEach(System.out::print);
(3) 通过BufferedReader.lines()
File file = new File("f:/file.txt");
BufferedReader buf = new BufferedReader(new FileReader(file));
Stream stream = buf.lines();
stream.forEach(System.out::println);
Stream
包括intermediate operation
和terminal operation
两种操作。
intermediate operation
操作返回的仍然是一个stream
,可以通过链式调用的方式简化代码编写。terminal operation
返回一个void
或者非stream
对象。 对于stream
使用的简单定义就是实现一个filter-map-reduce
的过程,产生一个最终结果或者造成一定的影响。
中间操作会返回一个新的流,并且操作是延迟执行的,不会修改原始数据,而且是在终点操作开始的时候才开始执行。
distinct
:保证输出的流中包含唯一的元素,案例 :
Stream<String> stream = Stream.of("a", "b", "c","c");
stream.distinct().forEach(System.out::println);
//输出 a b c
filter
:返回流中满足断言的数据案例 :
IntStream intStream = IntStream.range(0, 10);
List list = intStream.filter(i -> i % 2 == 0).boxed().collect(Collectors.toList());
System.out.println(list);
//输出 [0, 2, 4, 6, 8]
map
:将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同。案例 :
Stream<String> stream = Arrays.stream(new String[] {
"hello", "hi", "good" });
List<Integer> lengthList = stream.map(s -> s.length()).collect(Collectors.toList());
System.out.println(lengthList);
//输出 [5, 2, 4]
flatmap:方法混合了map + flattern的功能,它将映射后的流的元素全部放入到一个新的流中。
sorted
:将流中的元素按照自然排序方式进行排序,sorted(Comparator super T> comparator)可以指定排序的方式。对于有序流,排序是稳定的。对于非有序流,不保证排序稳定。
案例 :
Comparator comparator = (a, b) -> b.compareTo(a);
IntStream intStream = IntStream.range(0, 10);
List list = intStream.boxed().collect(Collectors.toList()).stream().sorted(comparator)
.collect(Collectors.toList());
System.out.println(list);
//输出 [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
limit
:返回指定数量的元素的流。对于串行流,这个方法是有效的,这是因为它只需返回前n个元素即可,但是对于有序的并行流,它可能花费相对较长的时间,如果你不在意有序,可以将有序并行流转换为无序的,可以提高性能。案例:
Comparator comparator = (a, b) -> b.compareTo(a);
IntStream intStream = IntStream.range(0, 10);
List list = intStream.boxed().collect(Collectors.toList()).stream().filter(i -> i % 2 == 0)
.sorted(comparator).limit(3).collect(Collectors.toList());
System.out.println(list);
//输出 [8, 6, 4]
skip
:返回丢弃了前n个元素的流,如果流中的元素小于或者等于n,则返回空的流案例:
IntStream intStream = IntStream.range(0, 10);
intStream.skip(3).boxed().collect(Collectors.toList()).forEach(System.out::print);
//输出 3456789
match
:用于匹配满足条件的流中的元素 allMatch只有在所有的元素都满足断言时才返回true,否则flase,流为空时总是返回true
anyMatch只有在任意一个元素满足断言时就返回true,否则flase,
noneMatch只有在所有的元素都不满足断言时才返回true,否则flase,
案例:
List intList = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(intList.stream().allMatch(i -> i < 5));
System.out.println(intList.stream().anyMatch(i -> i < 5));
System.out.println(intList.stream().noneMatch(i -> i > 5));
//输出 false true true
count
:方法返回流中的元素的数量。collect
:用于收集结果,使用其几乎能得到想要的任意数据的聚合. R collect(Supplier supplier,
BiConsumer accumulator,
BiConsumer combiner);
//第一个参数supplier为结果存放容器,第二个参数accumulator为结果如何添加到容器的操作,第三个参数combiner则为多个容器的聚合策略.
R collect(Collector super T, A, R> collector);
//通过Collectors工具类实现
案例:
@Test
public void collectTest1() {
List intList = Arrays.asList("1", "2", "3", "4");
System.out.println(intList.stream().collect(Collectors.joining(",")));
System.out.println(intList.stream().map(s -> new Integer(s)).collect(Collectors.averagingInt(i -> i)) + "");
}
//输出1,2,3,4 和 2.5
@Test
public void collectTest2() {
List intList = Arrays.asList("1", "2", "3", "4");
System.out.println(intList.stream().collect(StringBuilder::new, StringBuilder::append, StringBuilder::append));
}
//输出 1234
find
:用于查找满足条件的元素 findAny()返回任意一个元素,如果流为空,返回空的Optional,对于并行流来说,它只需要返回任意一个元素即可,所以性能可能要好于findFirst(),但是有可能多次执行的时候返回的结果不一样。
findFirst()返回第一个元素,如果流为空,返回空的Optional。
案例:
List intList = Arrays.asList(1, 2, 3, 4, 5, 6);
Optional findAny = intList.parallelStream().filter(num -> num % 3 == 0).findAny();
System.out.println(findAny.get());
//输出 6 或 3
forEach,forEachOrdered
:forEach遍历流的每一个元素,执行指定的action。
max/min
: 返回流中元素最大值/最小值
reduce
:返回单个的结果值,并且reduce操作每处理一个元素总是创建一个新值。常用的方法average, sum, min, max, count,使用reduce方法都可实现。
Optional reduce(BinaryOperator accumulator);
T reduce(T identity, BinaryOperator accumulator);
//参数 identity:循环计算的初始值。accumulator:计算的累加器,定义计算逻辑。
案例:
List<Integer> intList = Arrays.asList(1, 2, 3, 4);
System.out.println(intList.stream().reduce((r, s) -> r + s));
System.out.println(intList.stream().reduce(2, (r, s) -> r + s));
//输出 Optional[10] 和 12
List intList1 = Arrays.asList("a", "b", "c", "d");
List intList2 = Arrays.asList("1", "2", "3", "4");
Stream concatStream = Stream.concat(intList1.stream(), intList2.stream());
concatStream.forEach(System.out::print);
//输出 abcd1234
-- 数据量大且复杂,多核的情况下考虑。
所有的流操作都可以串行执行或者并行执行。
除非显示地创建并行流,否则Java库中创建的都是串行流。
Collection.stream()为集合创建串行流,Collection.parallelStream()为集合创建并行流。通过parallel()方法可以将串行流转换成并行流,sequential()方法将流转换成串行流。
parallel stream通过默认的ForkJoinPool实现并行处理。处理的过程采用分治进行,将整个任务切分成多个段,分别对各个段进行处理。通过parallel stream对数据进行处理会使数据失去原始的顺序性。
流的原始顺序性依赖于数据源的有序性。在使用并行流会改变流中元素的处理顺序,破坏流的原始顺序性,所以在使用并行流对数据源进行处理时应确定数据源的元素满足结合性。
可以使用forEachOrdered()
可以保持数据源原有的顺序性,或者通过sorted()
重新定义数据的顺序。
IntStream.range(1, 10).forEach(System.out::print);
System.out.println();
IntStream.range(1, 10).parallel().forEach(System.out::print);
System.out.println();
IntStream.range(1, 10).parallel().forEachOrdered(System.out::print);
//输出 123456789
// 658793241 随机
// 123456789
流可以从非线程安全的集合中创建,当流执行终点操作的时候,非concurrent数据源不应该被改变。
List list = new ArrayList<>();
list.add("one");
list.add("two");
list.stream().forEach(s -> list.add("three"));
//并发异常 :java.util.ConcurrentModificationException
而concurrent数据源可以被修改,不会出现并发问题。
List list = new CopyOnWriteArrayList<>(Arrays.asList("one", "two"));
list.stream().forEach(s -> list.add("three"));
//正常执行
由于stream的延迟操作特性,在执行终点操作前可以修改数据源,在执行终点操作时会将修改应用。
List list = new ArrayList<>();
list.add("one");
list.add("two");
Stream listStream = list.stream();
list.add("three");
listStream.forEach(System.out::println);
//输出 one two three