1. What is Stream?
Stream is an important new feature of Java 8. It provides functional programming support and allows pipeline operations to operate collections. Stream operations traverse the data source, use pipeline operations to process the data and generate a result collection. This process usually does not affect the data source. cause impact.
At the same time, stream is not a data structure, it is just a view of a certain data source. The data source can be an array, Java container or I/O channel , etc. Each operation in Stream will generate a new stream. Internally, the value will not be obtained immediately like ordinary collection operations. Instead, the value will be obtained lazily and will not be executed until the user actually needs the result .
Stream represents a data stream, and the number of data elements in the stream may be limited or unlimited.
The difference between streams and collections
-
No data is stored . A stream is an object based on a data source. It does not store data elements itself, but passes elements of the data source to operations through a pipeline.
-
Functional programming . Stream operations do not modify the data source, for example,
filter
they do not delete data in the data source. -
Delay operation . Many operations of the stream, such as filter, map and other intermediate operations, are delayed, and the operations will only be executed sequentially when the end operation is reached.
-
Can be untied . For an unlimited number of streams, some operations can be completed in a limited time, such as
limit(n)
orfindFirst()
. These operations can implement "short-circuiting" and return after accessing a limited number of elements. -
Pure consumption . The elements of the stream can only be accessed once, similar to Iterator, and there is no way back in the operation. If you want to revisit the elements of the stream from the beginning, sorry, you have to regenerate a new stream.
Collections are about data, streams are about calculations
Stream in Java 8 is an enhancement to the functionality of Collection objects. It focuses on performing various very convenient and efficient aggregation operations (aggregate operations) or bulk data operations (bulk data operations) on collection objects. The Stream API greatly improves programming efficiency and program readability with the help of the newly emerged Lambda expression.
At the same time, it provides serial and parallel modes for aggregation operations. The concurrent mode can make full use of the advantages of multi-core processors and use fork/join parallel methods to split tasks and accelerate the processing process. Writing parallel code is usually difficult and error-prone, but using the Stream API makes it easy to write high-performance concurrent programs without writing a single line of multi-threaded code. Therefore, java.util.stream, which first appeared in Java 8, is a product of the combined influence of functional language + multi-core era.
2. Overview of stream operations
There are two types of stream operations: intermediate operations, termination operations, and short-circuit operations.
1. Intermediate operations
A stream can be followed by zero or more intermediate operations. Its purpose is mainly to open the stream, perform some degree of data mapping/filtering, and then return a new stream for use by the next operation. This type of operation is lazy, that is, just calling this type of method does not actually start the traversal of the stream.
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
2. Terminate operation
A stream can only have one terminal operation. After this operation is executed, the stream is "lighted" and cannot be operated anymore. So this must be the last operation on the stream. The execution of the Terminal operation will actually start the traversal of the stream and generate a result or a side effect.
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
3. Short circuit operation
-
For an intermediate operation, if it accepts an infinite stream, it can return a finite new Stream.
-
For a terminal operation, if it accepts an infinite stream, but can calculate the result in a limited time.
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
Basic type Stream
IntStream, LongStream, DoubleStream , java provides corresponding Streams for these three basic numerical types.
Other numeric Streams are not provided in Java 8 because this would result in more amplified content. Conventional numerical aggregation operations can be performed through the above three Streams.
The construction of numerical streams
IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
IntStream.range(1, 3).forEach(System.out::println);
IntStream.rangeClosed(1, 3).forEach(System.out::println);
range() needs to pass in two parameters: the start node and the end node, and returns an ordered LongStream. Contains all parameters between the start node and the end node, with an interval of 1.
The function of rangeClosed is similar to range. The difference is that rangeClosed includes the final end node, while range does not.
3. Characteristics of flow
Parallelism
All stream operations can be executed serially or in parallel.
Unless you create parallel streams explicitly, all Java libraries create serial streams. Collection.stream()
Creates a serial stream for collections and Collection.parallelStream()
a parallel stream for collections. IntStream.range(int, int)
What is created is a serial stream. Methods can be used parallel()
to convert serial streams into parallel streams, and sequential()
methods can be used to convert streams into serial streams.
Unless the Javadoc of the method indicates that the result of the method is uncertain when executed in parallel (such as findAny, forEach), the results of serial and parallel execution should be the same.
Can't interfere
Streams can be created from non-thread-safe collections, and non-concurrent data sources should not be changed while the stream's pipeline is executing.
The following code will throw java.util.ConcurrentModificationException
an exception:
List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
sl.forEach(s -> l.add("three"));
When setting up intermediate operations, you can change the data source. Concurrency problems (throwing exceptions, or unexpected results) may occur only when executing end-point operations. For example, the following code will not throw exceptions:
List l = new ArrayList(Arrays.asList("one", "two"));
Stream sl = l.stream();
l.add("three");
sl.forEach(System.out::println);
For concurrent data sources, there will be no such problem. For example, the following code is normal:
List l = new CopyOnWriteArrayList<>(Arrays.asList("one", "two"));
Stream sl = l.stream();
sl.forEach(s -> l.add("three"));
Although our above example is to modify the non-concurrent data source in the terminal operation, the non-concurrent data source may also be modified in other threads, and there will also be concurrency issues.
no status
The parameters of most stream operations are functional interfaces, which can be implemented using Lambda expressions. They are used to describe user behavior and are called behavioral parameters.
If these behavioral parameters are stateful, the results of stream operations may be undefined, such as the following code:
List<String> l = new ArrayList(Arrays.asList("one", "two", ……));
class State {
boolean s;
}
final State state = new State();
Stream<String> sl = l.stream().map(e -> {
if (state.s)
return "OK";
else {
state.s = true;
return e;
}
});
sl.forEach(System.out::println);
The results of multiple executions of the above code may be different when executed in parallel. This is because this lambda expression is stateful.
side effect
After all operations on the pipeline are executed, where are the results (if any) required by the user? The first thing to explain is that not all Stream end operations need to return results. Some operations are just to use their side effects ( Side-effects ). For example, using Stream.forEach()
a method to print out the results is a common scenario of using side effects.
The use of behavioral parameters with . In fact, the use of side effects should be avoided in other scenarios except printing . You may think that collecting elements in Stream.forEach() is a good choice, as in the code below, but unfortunately the correctness and efficiency of such use cannot be guaranteed because Stream may execute in parallel. Most uses of side effects can be done more safely and efficiently using reduction operations .
Many behavior parameters with side effects can be converted into side-effect-free implementations
ArrayList<String> list = Lists.newArrayList();
for (int i = 0;i<1000;i++) {
list.add(i+"");
}
ArrayList<String> list2 = Lists.newArrayList();
// 副作用代码
list.parallelStream().forEach(s -> list2.add(s));
System.out.println(list2);
The result of the above code is that an error occurred in the ArrayList operation in multi-threaded state. At the same time, if the size of list2 is not specified, an ArrayIndexOutOfBoundsException subscript out-of-bounds exception may be reported when the list is expanded.
You can change it to the following code without side effects, or use the concurrent collection class CopyOnWriteArrayList instead
1 2 3 4 5 6 |
COPY ArrayList<String> list = Lists.newArrayList(); for (int i = 0;i<1000;i++) { list.add(i+""); } List<String> list2 = list.parallelStream().collect(Collectors.toList()); System.out.println(list2); |
sort
The elements returned by some streams are in a certain order, which we call encounter order . This order is the order in which the stream provides its elements. For example, the encounter order of an array is the sorting order of its elements, and the List is its iteration order. For HashSet, it has no encounter order itself.
Whether a stream is encounter order mainly depends on the data source and its intermediate operations. For example, the streams created on the data source List and Array are ordered (ordered), but the streams created on the HashSet are not ordered.
The method sorted()
can convert the stream into encounter order , unordered
and can convert the stream into encounter order .
Note that this method does not sort or scatter the elements, but returns a stream of encounter order .
In addition, an operation may affect the ordering of the stream, such as map
a method, which will replace the elements in the stream with different values or even types, so the ordering of the input elements has become meaningless, but for filter
methods In other words, it just discards some values, and the order of the input elements is still guaranteed.
For serial streams, whether the stream is ordered or not will not affect its performance, but will affect determinism. The results of an unordered stream may be different when executed multiple times.
For parallel streams, removing the ordering constraint may improve performance, for example distinct
, groupingBy
in these aggregation operations.
associativity
An operation or function
op
is associative if it satisfies the following conditions:
1 |
COPY (a on b) on c == a on (b on c) |
For concurrent streams, if the operations satisfy associativity, we can calculate in parallel:
1 |
COPYa to b to c to d == (a to b) to (c to d) |
For example min
, max
and string concatenation are all associative.
function object
When using Stream for functional programming, you often need to pass operations as parameters into the stream method, and the function object is the method or lambda expression as the object.
1 |
COPYList<String> strArray = Arrays.asList(stringArrays).stream().filter(x>x.contains("Tomas")).collect(Collectors.toList()); |
filter
The parameter in the above example x>x.contains("Tomas")
is a lambda expression.
Stream creation
Streams can be created in a variety of ways:
Create a stream from a collection
The Collection interface in Java8 has been extended to provide two methods for obtaining streams:
- Stream stream() : returns a sequential stream
- Stream parallelStream() : Returns a parallel stream
1 2 |
COPY Stream<Integer> stream1 = Arrays.asList(1,2,3,4).stream(); Stream<Integer> stream2 = Arrays.asList(1,2,3,4).parallelStream(); |
java.util.stream.Stream
It is one interface
, and the return values of various pipeline intermediate operations are its implementation classes, which allows us to conveniently pass parameters.
Create a stream from an array
The static method stream() of Arrays in Java8 can obtain an array stream: static Stream stream( T[] array ) : returns an overloaded form of a stream that can handle arrays of corresponding basic types.
Arrays也提供了创建流的静态方法
stream():
1 |
COPYArrays.stream(new int[]{1,2,3}) |
Create a stream from a value
You can use the static method Stream.of() to create a stream by displaying values, which can receive any number of parameters.
public static Stream of(T… values) : Returns a stream
Stream
The static methods of()
can also be used to create streams:
1 |
COPYStream<String> stream3 = Stream.of(new String[]{"1","2","3","4"}); |
Create a stream from a method
You can use the static methods Stream.iterate() and Stream.generate() to create infinite streams
迭代流 : public static Stream iterate(final T seed, final UnaryOperator f)
1 2 |
COPY //Infinite geometric sequence with initial value 1 Stream.iterate(1, n -> n * 2); |
Generating stream: public static Stream generate(Supplier s)
1 2 |
COPY//Infinite random number stream Stream.generate(Math::random) |
Use the static methods of IntStream, LongStream, and DoubleStream to create finite streams
1 2 3 |
COPYIntStream.of(new int[]{1, 2, 3}); IntStream.range(1, 3); IntStream.rangeClosed(1, 3); |
Create an infinite stream of values using the ints() method of the random number class
1 2 |
COPYRandom random = new Random(); IntStream ints = random.ints(); |
Get stream from file
Obtain a stream of lines from a file using the lines method of BufferedReader
1 2 |
COPYBufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("file.txt"))); Stream<String> lines = bufferedReader.lines(); |
Files
类的操作路径的方法,如list
、find
、walk
等。
其他类提供的创建流
一些类也提供了创建流的方法:
BitSet数值流
1 |
COPY IntStream stream = new BitSet().stream(); |
Pattern 将字符串分隔成流
1 2 3 |
COPY Pattern pattern = compile(","); Stream<String> stringStream = pattern.splitAsStream("a,b,c,d"); stringStream.forEach(System.out::println); |
JarFile 读取jar文件流
1 |
COPY Stream<JarEntry> stream = new JarFile("").stream(); |
更底层的使用StreamSupport
,它提供了将Spliterator
转换成流的方法,目前还不了解
中间操作
流操作是惰性执行的, 中间操作会返回一个新的流对象, 当执行终点操作时才会真正进行计算,下面介绍流的中间操作,除非传入的操作函数有副作用, 函数本身不会对数据源进行任何修改。
这个Scala集合的转换操作不同,Scala集合转换操作会生成一个新的中间集合,显而易见Java的这种设计会减少中间对象的生成。
distinct 唯一
distinct
保证数据源中的重复元素在结果中只出现一次, 它使用equals()
方法判断两个元素是否相等.
1 2 |
COPY Stream<String> stream3 = Stream.of(new String[]{"1", "2", "3", "4", "1", "2", "3", "4"}); System.out.println(stream3.distinct().collect(Collectors.toList())); |
filter 过滤
filter
根据传入的断言函数对所有元素进行检查, 只有使断言函数返回真的元素才会出现在结果中.filter
不会对数据源进行修改.
1 2 3 |
COPY Stream<String> stream3 = Stream.of(new String[]{"1", "2", "3", "4", "6", "7", "8", "9"}); List<String> stringList = stream3.filter(x-> Integer.parseInt(x)%2==0).collect(Collectors.toList()); System.out.println(stringList); |
map 映射
map方法根据传入的mapper函数对元素进行一对一映射, 即数据源中的每一个元素都会在结果中被替换(映射)为mapper函数的返回值,也可以根据处理返回不同的数据类型。
1 2 3 |
COPY Stream<String> stream3 = Stream.of(new String[]{"1", "2", "3", "4", "6", "7", "8", "9"}); List<Integer> integerList = stream3.map(x -> Integer.parseInt(x)).collect(Collectors.toList()); System.out.println(integerList); |
flatmap 映射汇总
flatmap方法混合了map + flattern的功能,同时扩展flatMapToDouble、flatMapToInt、flatMapToLong提供了转换成特定流的方法。它将映射后的流的元素全部放入到一个新的流中。它的方法定义如下:
1 |
COPY <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper) |
flatmap适用于多对多或者一对多的映射关系,mapper函数会将每一个元素转换成一个流对象,而flatMap方法返回的一个流包含所有mapper转换后的元素。
下面举个例子来详细说明:
给定一个列表{“aaa”,”bbb”,”ddd”,”eee”,”ccc”}。需要在控制台直接输出aaabbbdddeeeccc字样采用map来做
1 2 3 4 5 6 7 8 9 10 |
COPY List<String> list = Arrays.asList("aaa", "bbb", "ddd", "eee", "ccc"); //这里采用了两次forEach循环进行输出,显然不太优雅 list.stream().map(x -> { List<Character> characterList = new ArrayList<>(); char[] chars = x.toCharArray(); for (char c : chars) { characterList.add(c); } return characterList.stream(); }).forEach(xStream -> xStream.forEach(System.out::print)); //aaabbbdddeeeccc |
采用flatMap来做
1 2 3 4 5 6 7 8 9 10 |
COPY List<String> list = Arrays.asList("aaa", "bbb", "ddd", "eee", "ccc"); //采用flatMap来做 体会一下flatMap的魅力吧 list.stream().flatMap(x -> { List<Character> characterList = new ArrayList<>(); char[] chars = x.toCharArray(); for (char c : chars) { characterList.add(c); } return characterList.stream(); }).forEach(System.out::print); //aaabbbdddeeeccc |
limit 截断
limit
方法指定数量的元素的流。对于串行流,这个方法是有效的,这是因为它只需返回前n个元素即可,但是对于有序的并行流,它可能花费相对较长的时间,如果你不在意有序,可以将有序并行流转换为无序的,可以提高性能。
limit(int n)
当流中元素数大于n时丢弃超出的元素, 否则不进行处理, 达到限制流长度的目的.
1 2 3 |
COPY Stream<Integer> stream3 = Stream.of(3,5,1,4,2,6,8,7); List<Integer> integerList = stream3.limit(3).collect(Collectors.toList()); System.out.println(integerList); |
peek 观察者
生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数;这里所说的消费函数有点类似于钩子,每个元素被消费时都会执行这个钩子
peek方法会对数据源中所有元素进行给定操作, 但在结果中仍然是数据源中的元素. 通常我们利用操作的副作用, 修改其它数据或进行输入输出.
peek接收一个没有返回值的λ表达式,可以做一些输出,外部处理等。map接收一个有返回值的λ表达式,之后Stream的泛型类型将转换为map参数λ表达式返回的类型
1 2 3 |
COPY Stream<Integer> stream3 = Stream.of(1,2,3,4,5,6,7,8,9); List<Integer> integerList = stream3.peek(x-> System.out.println(x)).collect(Collectors.toList()); System.out.println(integerList); |
sorted 排序
sorted()将流中的元素按照自然排序方式进行排序,如果元素没有实现Comparable,则终点操作执行时会抛出java.lang.ClassCastException异常。
sorted(Comparator<? super T> comparator)可以指定排序的方式。
对于有序流,排序是稳定的。对于非有序流,不保证排序稳定。
sorted
方法用于对数据源进行排序:
1 2 3 |
COPY Stream<Integer> stream3 = Stream.of(4, 5, 2, 6, 9, 0, 1, 3, 6, 8); List<Integer> integerList = stream3.sorted((x, y) -> x - y).collect(Collectors.toList()); System.out.println(integerList); |
使用
java.util.Comparator
是更方便的方法, 默认进行升序排序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
COPY class Item { public Item(int value) { this.value = value; } private int value; public int getValue() { return value; } public void setValue(int value) { this.value = value; } } Stream<Item> stream3 = Stream.of(new Item(4), new Item(3), new Item(6), new Item(9)); List<Item> itemList = stream3.sorted(Comparator.comparingInt(Item::getValue)).collect(Collectors.toList()); itemList.forEach(x -> System.out.print(x.getValue()+",")); |
使用
reversed()
方法进行降序排序:
1 2 3 |
COPY Stream<Item> stream3 = Stream.of(new Item(4), new Item(3), new Item(6), new Item(9)); List<Item> itemList = stream3.sorted(Comparator.comparingInt(Item::getValue).reversed()).collect(Collectors.toList()); itemList.forEach(x -> System.out.print(x.getValue()+",")); |
skip 跳过
skip(int)
返回丢弃了前n个元素的流. 如果流中的元素小于或者等于n,则返回空的流
1 2 3 |
COPY Stream<Integer> stream3 = Stream.of(3,5,1,4,2,6,8,7); List<Integer> integerList = stream3.skip(3).collect(Collectors.toList()); System.out.println(integerList); |
终点操作
match 断言
1 2 3 |
COPY public boolean allMatch(Predicate<? super T> predicate) public boolean anyMatch(Predicate<? super T> predicate) public boolean noneMatch(Predicate<? super T> predicate) |
这一组方法用来检查流中的元素是否满足断言。
-
allMatch
只有在所有的元素都满足断言时才返回true,否则flase,流为空时总是返回true -
anyMatch
只有在任意一个元素满足断言时就返回true,否则flase, -
noneMatch
只有在所有的元素都不满足断言时才返回true,否则flase,
1 2 3 4 5 6 |
COPY System.out.println(Stream.of(1,2,3,4,5).allMatch( i -> i > 0)); //true System.out.println(Stream.of(1,2,3,4,5).anyMatch( i -> i > 0)); //true System.out.println(Stream.of(1,2,3,4,5).noneMatch( i -> i > 0)); //false System.out.println(Stream.<Integer>empty().allMatch( i -> i > 0)); //true System.out.println(Stream.<Integer>empty().anyMatch( i -> i > 0)); //false System.out.println(Stream.<Integer>empty().noneMatch( i -> i > 0)); //true |
count 计数
count方法返回流中的元素的数量。
1 2 |
COPY String[] arr = new String[]{"a","b","c","d"}; long count = Arrays.stream(arr).count(); |
你也可以手动来实现它
1 2 |
COPY String[] arr = new String[]{"a","b","c","d"}; long count = Arrays.stream(arr).mapToLong(x->1L).sum(); |
collect 收集
collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法。辅助类Collectors提供了很多的collector收集器,可以满足我们日常的需求,你也可以创建新的collector实现特定的需求。它是一个值得关注的类,你需要熟悉这些特定的收集器,如聚合类averagingInt、最大最小值maxBy minBy、计数counting、分组groupingBy、字符串连接joining、分区partitioningBy、汇总summarizingInt、化简reducing、转换toXXX等。
收集器
Collectors里常用搜集器介绍:
方法 | 返回类型 | 作用 |
---|---|---|
toList() | List | 把流中元素收集到List |
List result = list.stream().collect(Collectors.toList()); | ||
toSet() | Set | 把流中元素收集到Set |
Set result = list.stream().collect(Collectors.toSet()); | ||
toCollection() | Collection | 把流中元素收集到集合 |
Collection result = lsit.stream().collect(Collectors.toCollection(ArrayListL::new)); | ||
counting() | Long | 计算流中元素的个数 |
long count = lsit.stream().collect(Collectors.counting()); | ||
summingInt() | Integer | 对流中元素的整数属性求和 |
int total = lsit.stream().collect(Collectors.counting()); | ||
averagingInt | Double | 计算元素Integer属性的均值 |
double avg = lsit.stream().collect(Collectors.averagingInt(Student::getAge)); | ||
summarizingInt | IntSummaryStatistics | 收集元素Integer属性的统计值 |
IntSummaryStatistics result = list.stream().collect(Collectors.summarizingInt(Student::getAge)); | ||
**joining ** | Stream | 连接流中的每个字符串 |
String str = list.stream().map(Student::getName).collect(Collectors.joining()); | ||
**maxBy ** | Optional | 根据比较器选择最大值 |
Opetional max = list.stream().collect(Collectors.maxBy(comparingInt(Student::getAge))) | ||
**minBy ** | Optional | 根据比较器选择最小值 |
Optional min= list.stream().collect(Collectors.minBy(comparingInt(Student::getAge))); | ||
**reducing ** | 规约产生的类型 | 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 |
int total = list.stream().collect(Collectors.reducing(0, Student::getAge, Integer::sum)); | ||
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果转换 |
int how = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); | ||
groupingBy | Map<K, List> | 根据某属性值对流分组,属性为K,结果为V |
Map<Integer, List> map = list.stream().collect(Collectors.groupingBy(Student::getStatus)); | ||
partitioningBy | Map<Boolean, List> | 根据true或false进行分区 |
Map<Boolean, List> map = list.stream().collect(Collectors.partitioningBy(Student::getPass)); |
示例
collect
是使用最广泛的终点操作, 也上文中多次出现:
1 2 3 |
COPY List<String> list = Stream.of("a","b","c","b") .distinct() .collect(Collectors.toList()) |
toList()
将流转换为List
实例, 是最常见的用法, java.util.Collectors
类中还有求和, 计算均值, 取最值, 字符串连接等多种收集方法。
find 返回
findAny()
返回任意一个元素,如果流为空,返回空的Optional,对于并行流来说,它只需要返回任意一个元素即可,所以性能可能要好于findFirst(),但是有可能多次执行的时候返回的结果不一样。
findFirst()
返回第一个元素,如果流为空,返回空的Optional。
forEach 遍历
forEach遍历流的每一个元素,执行指定的action。它是一个终点操作,和peek方法不同。这个方法不担保按照流的encounter order顺序执行,如果对于有序流按照它的encounter order顺序执行,你可以使用forEachOrdered方法。
forEach
方法对流中所有元素执行给定操作, 没有返回值.
1 |
COPY Stream.of(1,2,3,4,5).forEach(System.out::println); |
嵌套遍历(不推荐)
如果要对两个集合进行遍历操作,可以将流嵌套,但是这种遍历的性能跟跟foreach嵌套一样,而且不能进行更复杂的操作,不推荐。
1 2 3 4 5 6 7 |
COPY ArrayList<String> list = Lists.newArrayList("1", "2"); ArrayList<String> list2 = Lists.newArrayList("一", "二"); list.stream().forEach(str1->{ list2.stream().forEach(str2->{ System.out.println(str1+str2); }); }); |
max、min 最大最小值
max返回流中的最大值,
min返回流中的最小值。
1 2 3 4 5 6 7 |
COPY ArrayList<Integer> list = Lists.newArrayList(3,5,2,1); Integer max = list.stream().max(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 - o2; } }).get(); |
concat 组合
concat(Stream a, Stream b)用来连接类型一样的两个流。
1 2 3 |
COPY List<Integer> list1 = Arrays.asList(1,2,3); List<Integer> list2 = Arrays.asList(4,3,2); Stream.concat(list1.stream(),list2.stream()).forEach(System.out::print); |
toXXX 转换
toArray方法将一个流转换成数组,而如果想转换成其它集合类型,西需要调用collect方法,利用Collectors.toXXX方法进行转换。
toArray()
将流中的元素放入到一个数组中,默认为Object数组
他还有一个重载方法可以返回指定类型的数组
1 2 |
COPY Object[] objects = Stream.of(1, 2, 3, 4, 5).toArray(); Integer[] integers = Stream.of(1, 2, 3, 4, 5).toArray(Integer[]::new); |
reduce 归约
reduce是常用的一个方法,事实上很多操作都是基于它实现的。
方法重载
它有几个重载方法:
方法 | 描述 |
---|---|
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值,返回 Optional |
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值,返回 T |
reduce(U identity, BiFunction a, BinaryOperator combiner) | 可以将流中元素反复结合起来,得到 |
PS: BinaryOperator 函数式接口,也即Lambada表达式
reduce思想
reduce是很重要的一种编程思想。这里重点介绍一下。reduce的作用是把stream中的元素给组合起来。至于怎么组合起来:
它需要我们首先提供一个起始种子,然后依照某种运算规则使其与stream的第一个元素发生关系产生一个新的种子,这个新的种子再紧接着与stream的第二个元素发生关系产生又一个新的种子,就这样依次递归执行,最后产生的结果就是reduce的最终产出,这就是reduce的算法最通俗的描述;
所以运用reduce我们可以做sum,min,max,average,所以这些我们称之为针对具体应用场景的reduce,这些常用的reduce,stream api已经为我们封装了对应的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
COPY //求和 sum List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5); // 没有起始值时返回为Optional类型 Optional<Integer> sumOptional = integers.stream().reduce(Integer::sum); System.out.println(sumOptional.get()); //15 // 可以给一个起始种子值 Integer sumReduce = integers.stream().reduce(0, Integer::sum); System.out.println(sumReduce); //15 //直接用sum方法 Integer sum = integers.stream().mapToInt(i -> i).sum(); System.out.println(sum); //15 |
第三个重载
前面两个方法比较简单,重点说说三个参数的reduce(U identity, BiFunction a, BinaryOperator combiner)
三个参数时是最难以理解的。 分析下它的三个参数:
- identity: 一个初始化的值;这个初始化的值其类型是泛型U,与Reduce方法返回的类型一致;注意此时Stream中元素的类型是T,与U可以不一样也可以一样,这样的话操作空间就大了;不管Stream中存储的元素是什么类型,U都可以是任何类型,如U可以是一些基本数据类型的包装类型Integer、Long等;或者是String,又或者是一些集合类型ArrayList等;后面会说到这些用法。
- accumulator: 其类型是BiFunction,输入是U与T两个类型的数据,而返回的是U类型;也就是说返回的类型与输入的第一个参数类型是一样的,而输入的第二个参数类型与Stream中元素类型是一样的
- combiner: 其类型是BinaryOperator,支持的是对U类型的对象进行操作,第三个参数combiner主要是使用在并行计算的场景下;如果Stream是非并行时,它实际上是不生效的。
因此针对这个方法的分析需要分并行与非并行两个场景。
就是因为U和T不一样,所以给了我们更多的发挥。比如设U的类型是ArrayList,那么可以将Stream中所有元素添加到ArrayList中再返回了,如下示例:
1 2 3 4 5 6 |
COPY ArrayList<String> result = Stream.of("aa", "ab", "c", "ad").reduce(new ArrayList<>(), (u, s) -> { u.add(s); return u; }, (strings, strings2) -> strings); System.out.println(result); //[aa, ab, c, ad] |
注意由于是非并行的,第三个参数实际上没有什么意义,可以指定r1或者r2为其返回值,甚至可以指定null为返回值。下面看看并行的情况:
当Stream是并行时,第三个参数就有意义了,它会将不同线程计算的结果调用combiner做汇总后返回。注意由于采用了并行计算,前两个参数与非并行时也有了差异! 看个例子:
1 2 3 4 5 |
COPY Integer reduce = Stream.of(1, 2, 3).parallel().reduce( 4, (integer, integer2) -> integer + integer2, (integer, integer2) -> integer + integer2); System.out.println(reduce); //18 |
输出:18
omg,结果竟然是18。显然串行的话结果是10;这个不太好理解,但是我下面写一个等价的方式,可以帮助很好的理解这个结果:
1 2 |
COPY Optional<Integer> reduce = Stream.of(1, 2, 3).map(n -> n + 4).reduce((s1, s2) -> s1 + s2); System.out.println(reduce.get()); //18 |
这种方式有助于理解并行三个参数时的场景,实际上就是第一步使用accumulator进行转换(它的两个输入参数一个是identity, 一个是序列中的每一个元素),由N个元素得到N个结果;第二步是使用combiner对第一步的N个结果做汇总。
reduce能干什么
好了,三个参数的reduce先介绍到这。下面继续看看reduce能为我们做什么?
1 2 3 4 5 6 7 8 |
COPY //构造字符串流 List<String> strs = Arrays.asList("H", "E", "L", "L", "O"); // reduce String concatReduce = strs.stream().reduce("", String::concat); System.out.println(concatReduce); //HELLO Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5); Integer minReduce = integerStream.reduce(Integer.MAX_VALUE, Integer::min); System.out.println(minReduce); //1 |
并发问题
除非显式地创建并行流, 否则默认创建的都是串行流.Collection.stream()
为集合创建串行流,而Collection.parallelStream()
创建并行流.
stream.parallel()
方法可以将串行流转换成并行流,stream.sequential()
方法将流转换成串行流.
1 2 |
COPY Stream<Integer> stream3 = Stream.of(1,2,3,4,5,6,7,8,9); stream3.forEach(x-> System.out.print(x+",")); |
输出
1,2,3,4,5,6,7,8,9,
流可以在非线程安全的集合上创建, 流操作不应该对非线程安全的数据源产生任何副作用, 否则将发生java.util.ConcurrentModificationException
异常.
1 2 |
COPY List<String> list = new ArrayList(Arrays.asList("x", "y")); list.stream().forEach(x-> list.add("z")); |
输出
1 2 3 4 |
COPY Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1388) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) at com.test.lambda.LambdaTest.main(LambdaTest.java:15) |
对于线程安全的容器不会存在这个问题:
1 2 3 4 5 |
COPY List<String> list = new CopyOnWriteArrayList(Arrays.asList("x", "y")); list.stream().forEach(x->{ list.add("z"); System.out.println(list); }); |
输出
[x, y, z]
[x, y, z, z]
当然作者建议Stream操作不要对数据源进行任何修改. 当然, 修改其它数据或者输入输出是允许的:
1 2 3 4 5 |
COPY Set<String> set = new HashSet<String>(); List<String> list = new CopyOnWriteArrayList(Arrays.asList("x", "y")); list.stream().forEach(x->{ set.add(x); }); |
理想的管道操作应该是无状态且与访问顺序无关的. 无状态是指操作的结果只与输入有关, 下面即是一个有状态的操作示例:
1 2 3 4 5 6 7 8 9 10 |
COPY State state = getState(); List<String> list = new ArrayList(Arrays.asList("a", "b")); list = list.stream().map(s -> { if (state.isReady()) { return s; } else { return null; } }); |
无状态的操作保证无论系统状态如何管道的行为不变, 与顺序无关则有利于进行并行计算.
函数式接口
函数式接口会将签名匹配的函数对象(lambda表达式或方法)视作接口的实现。
1 2 3 4 5 |
COPY @FunctionalInterface interface Greeter { void hello(String message); } |
函数式接口中有且只有一个非抽象方法。
1 |
COPY Greeter greeter = message -> System.out.println("Hello " + message); |
这在 Java 8 之前通常使用匿名内部类实现的:
1 2 3 4 5 6 |
COPY Greeter greeter = new Greeter() { @Override public void hello(String message) { System.out.println("Hello " + message); } }; |
Java 8 将已有的一些接口实现为函数式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.util.Comparator
- java.lang.reflect.InvocationHandler
- java.io.FileFilter
- java.nio.file.PathMatcher
java.util.function
中定义了一些常用的函数式接口:
- Consumer: 接受参数无返回
Consumer<T>
->void accept(T t)
;BiConsumer<T,U>
->void accept(T t, U u);
DoubleConsumer
->void accept(double value);
- Supplier: 不接受参数有返回
Supplier<T>
->T get();
DoubleSupplier
->double getAsDouble();
- Function: 接受参数并返回
Function<T, R>
->R apply(T t);
BiFunction<T, U, R>
->R apply(T t, U u);
DoubleFunction<R>
->R apply(double value);
DoubleToIntFunction
->int applyAsInt(double value);
BinaryOperator<T>
extendsBiFunction<T,T,T>
- Predicate: 接受参数返回boolean
Predicate<T>
->boolean test(T t);
BiPredicate<T, U>
->boolean test(T t, U u);
DoublePredicate
->boolean test(double value);
默认构造器可以作为supplier: Supplier<Item> supplier = Item::new;