Java 8 Stream介绍及使用

Posted 冰冻开水

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 8 Stream介绍及使用相关的知识,希望对你有一定的参考价值。

(原)

stream的内容比较多,先简单看一下它的说明:

A sequence of elements supporting sequential and parallel aggregate
* operations.  The following example illustrates an aggregate operation using
* {@link Stream} and {@link IntStream}:
*
* <pre>{@code
*     int sum = widgets.stream()
*                      .filter(w -> w.getColor() == RED)
*                      .mapToInt(w -> w.getWeight())
*                      .sum();
* }</pre>
* In this example, {@code widgets} is a {@code Collection<Widget>}.  We create
* a stream of {@code Widget} objects via {@link Collection#stream Collection.stream()},
* filter it to produce a stream containing only the red widgets, and then
* transform it into a stream of {@code int} values representing the weight of
* each red widget. Then this stream is summed to produce a total weight.

一个元素的序列,它支持一个串行和并行的聚合操作。下面的例子介绍了使用stream和intstream进行聚合操作。

这个例子里,widgets是一个集合,我们通过Collection.stream()创建了一个Widget的Stream对象。然后过滤了Color属性为RED的对象,然后将其转换成一个int值的Stream,它表示每个红色的重量,然后通过 sum将这些值累加起来。

通过文档能让我们大概的了解stream是怎样操作的。

* <p>In addition to {@code Stream}, which is a stream of object references,
* there are primitive specializations for {@link IntStream}, {@link LongStream},
* and {@link DoubleStream}, all of which are referred to as "streams" and
* conform to the characteristics and restrictions described here.
除了Stream之外(它是一种对象引用流),除此之外,还有一些针对于原生类型的聚化,例如IntStream、LongStream、DoubleStream,它们都被称作为“Stream”,并且它们都遵循这里所描述的stream的特性和限制。

* <p>To perform a computation, stream
* <a href="package-summary.html#StreamOps">operations</a> are composed into a
* <em>stream pipeline</em>.  A stream pipeline consists of a source (which
* might be an array, a collection, a generator function, an I/O channel,
* etc), zero or more <em>intermediate operations</em> (which transform a
* stream into another stream, such as {@link Stream#filter(Predicate)}), and a
* <em>terminal operation</em> (which produces a result or side-effect, such
* as {@link Stream#count()} or {@link Stream#forEach(Consumer)}).
* Streams are lazy; computation on the source data is only performed when the
* terminal operation is initiated, and source elements are consumed only
* as needed.
通过Stream的计算,它的操作会将它组合到一个Stream管道中(Stream特别像Linux里面管道的概念),一个流的管道包含的源可能是一个数组、一个集合、一个函数或是IO channel,在Stream的计算中可能会包括一个或多个中间操作(这些中间操作会将这个Stream转换成另一个Stream,例如通过Stream#filter(Predicate)),和一个中止操作(它会产生一个结果,如Stream#count()),Stream是惰性和,它的源数据只有在终止操作初始化的时候才执行,源数据只有被需要的时候才会被消费。
上面的意思是说,比如。Stream.aaa().bbb().ccc().count();
aaa,bbb,ccc方法为Stream的中间操作。在最的一个count未被调用时,这些中间操作是不会被执行的,这意味这count是一个终止操作。

根据文档的描述,Stream的计算由三部分组中:
1、     源数据
2、     中间操作
3、     终止操作
这里的中间操作属于惰性求值,而终止操作属于及早求值。
那么Stream要怎样创建呢?
看这个方法:
/**
 * Returns a sequential ordered stream whose elements are the specified values.
 *
 * @param <T> the type of stream elements
 * @param values the elements of the new stream
 * @return the new stream
 */
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}
根据指定的元素返回一个有序有Stream
于是我们可以这样创建:
Stream stream1 = Stream.of("zhangsan", "lisi", "wangwu");
或者
String[] str2 = {"zhangsan", "lisi", "wangwu"};
Stream stream2 = Stream.of(str2);
或者通过List.stream()
List list3 = Arrays.asList("zhangsan", "lisi", "wangwu");
Stream stream3 = list3.stream();

我们通过IntStream创建流并将其输出:

IntStream.of(3,1,4,9).forEach(System.out::println);
在IntStream中有range和rangeClosed二个方法,接收二个参数,代表着一个整数区间范围,range表示不包含最后一位,rangeClosed表示包含最后一位。
IntStream.range(1,4).forEach(System.out::println);
System.out.println("---");
IntStream.rangeClosed(1,4).forEach(System.out::println);

如果现在有一个Int集合,要让集合里面的所有数值乘以2再相加得到最后的结果,用以前的写法不得不用循环的方式这么写:
List<Integer> partList3 = Arrays.asList(1,2,3);
Integer sum3 = 0;
for (Integer o : partList3) {
    sum3 += o * 2;
}
而通过Stream可以化成一行代码:
Integer sum33 = partList3.stream().map(integer -> integer * 2).reduce(0,Integer::sum);
/**
 * Returns a stream consisting of the results of applying the given
 * function to the elements of this stream.
 *
 * <p>This is an <a href="package-summary.html#StreamOps">intermediate
 * operation</a>.
 *
 * @param <R> The element type of the new stream
 * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
 *               <a href="package-summary.html#Statelessness">stateless</a>
 *               function to apply to each element
 * @return the new stream
 */
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
 
这里的map方法接收一个Function参数,最后会返回一个Stream对象,记住,这里是一个中间操作。Map方法中的Function函数会传入一个参数,然后返回把这个参数经过处理后的结果,reduce方法是一个终止操作,将这些值都通过方法引用Integer::sum的方式加起来。其中reduce接收二个参数:

  T reduce(T identity, BinaryOperator<T> accumulator);

第一个参数可以认为是初始值,第二个参数是计算方式的函数,计算结果等价于:
T result = identity;
for (T element : this stream)
    result = accumulator.apply(result, element)
    return result;
}
如果将一个Stream对象转成一数组?
Stream<String> stream4 = Stream.of("a", "k", "g", "b");
Stream中有一个toArray方法。

/**
 * Returns an array containing the elements of this stream, using the
 * provided {@code generator} function to allocate the returned array, as
 * well as any additional arrays that might be required for a partitioned
 * execution or for resizing.
 *
 * <p>This is a <a href="package-summary.html#StreamOps">terminal
 * operation</a>.
 *
 * @apiNote
 * The generator function takes an integer, which is the size of the
 * desired array, and produces an array of the desired size.  This can be
 * concisely expressed with an array constructor reference:
 * <pre>{@code
 *     Person[] men = people.stream()
 *                          .filter(p -> p.getGender() == MALE)
 *                          .toArray(Person[]::new);
 * }</pre>
 *
 * @param <A> the element type of the resulting array
 * @param generator a function which produces a new array of the desired
 *                  type and the provided length
 * @return an array containing the elements in this stream
 * @throws ArrayStoreException if the runtime type of the array returned
 * from the array generator is not a supertype of the runtime type of every
 * element in this stream
 */
<A> A[] toArray(IntFunction<A[]> generator)
这里除了使用之前说过的这种方式。
String[] streamArr1 = stream4.toArray(len -> new String[len]);
因为IntFunction这个函数接收一个Int类型为数组长度,返回一个数组的类型,我们也可以用文档中提供的例子这种方法。
String[] streamArr2 = stream4.toArray(String[]::new);
 
如果将一个Stream转换成List
Stream中提供了2个重载的方法
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
 
<R, A> R collect(Collector<? super T, A, R> collector);
下面一个方法可以看作是上面的缩写形式,
第一个方法中,文档中如此描述:
Performs a <a href="package-summary.html#MutableReduction">mutable
* reduction</a> operation on the elements of this stream.  A mutable
* reduction is one in which the reduced value is a mutable result container,
* such as an {@code ArrayList}, and elements are incorporated by updating
* the state of the result rather than by replacing the result.  This
* produces a result equivalent to:
* <pre>{@code
*     R result = supplier.get();
*     for (T element : this stream)
*         accumulator.accept(result, element);
*     return result;
* }</pre>
对这个流中的元素执行一个可变的汇聚的操作,一个可变的汇聚的操作指的是被汇聚的值是一个可变的结果容器,比如ArrayList, 这个集合是通过更新结果状态来合并的,而不是通过替换这个结果来合并的,这个结果等同于:
R result = supplier.get();
   for (T element : this stream)
        accumulator.accept(result, element);
   return result;
 }
参数1:Supplier,返回的对象是该方法返回的对象类型。
参数2:BiConsumer,接收的第一个参数可以理解是一个需要汇聚的对象,第二个参数是stream中的某一个元素。
参数3:BiConsumer,相当于是一个合并器,将上一次的结果和装进同一个对象中,用于最后的返回。当然这个对象的类型就是Supplier返回的对象类型。
例如这样:
Stream<String> stream5 = Stream.of("hello", "world", "good", "morning");

List<String> list5 = stream5.collect(() -> new ArrayList<>(),(list1,item) -> list1.add(item),(list2,list1) -> list2.addAll(list1));

list5.forEach(System.out::println);
这里有个简单的方法可以替代以上的代码
Stream<String> stream444 = Stream.of("list1", "list2", "list3");
List<String> list444 = stream444.collect(Collectors.toList());
list444.forEach(System.out::println);
Collectors中有很多方法,toList()只是将stream中的值存入ArrayList中返回。
如果想要换成LinkedList,可以用Collectors.toCollection(LinkedList::new)
如果要将里面的值拼接成一个字符串返回,可以用Collectors.joining()
在stream中,map作为一个映射方法,可以将集合中的无素作相处理,但如果集合中的每个元素都是一个list,要将这些list中的元素合并成一个集合,以前的做法就是用list.addAll()这个方法,现在可以利用stream中的另一个映射方法,floatMap.
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
它接收一个 Function函数,返回值为stream
于是可以这样写:
Stream<List<Integer>> stream7 = Stream.of(Arrays.asList(1,2), Arrays.asList(3), Arrays.asList(4,5));
List<Integer> list7 = stream7.flatMap(list -> list.stream()).collect(Collectors.toList());
System.out.println(list7);

将每个stream中的元素都展开,放在一个stream中,最后用collect方法放一个集合中输出。

stream中另一些比较常用的方法.
1、
public static<T> Stream<T> generate(Supplier<T> s) 
通过generate返回一个Stream对象,接收一个 Supplier函数
例如:
Stream stream8 = Stream.generate(UUID.randomUUID()::toString);
2、
Optional<T> findFirst(); 
This is a short-circuiting terminal operation
这是一个终止操作,返回一个Optional,为什么是Optional而不是一个对象T呢?因为Stream中可能会有空的情况,就像这样Stream stream8 = Stream.empty();
如果是这样,那么findFirst()可能会返回null值,所以才用了Optional来规避空值的情况。
例如:
Stream stream8 = Stream.generate(UUID.randomUUID()::toString);
stream8.findFirst().ifPresent(System.out::println);
3、
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

Returns an infinite sequential ordered {@code Stream} produced by iterative
application of a function {@code f} to an initial element {@code seed}

producing a {@code Stream} consisting of {@code seed}, {@code f(seed)},
*{@code f(f(seed))}, etc.

用这个种子元素seed,通过迭代f,返回一个无限的串行的有序的流元素,
最后的stream的结果是一个seed ,f(seed),f(f(seed)) … 的结果。
由于这个方法是无限循环的,所以往往会跟limit终止操作配合使用。
它的第2个参数UnaryOperator是这样定义的:
public interface UnaryOperator<T> extends Function<T, T>
如果它的apply方法接收参数和返回参数是同一类型,是一个一元的Function函数
例如:
Stream.iterate(1,i -> i + 2).limit(3).forEach(System.out::println);
返回的结果将是1、3、5。
4、

Stream<T> limit(long maxSize);

Returns a stream consisting of the elements of this stream, truncated
to be no longer than {@code maxSize} in length
返回一个长度不超过给定参数maxSize大小的stream

Stream<T> skip(long n);

Returns a stream consisting of the remaining elements of this stream
after discarding the first {@code n} elements of the stream

返回一个丢弃了前n个元素之后的stream。
例如:iterate生成一个从1开始往后加1的stream,先限定这个stream长度为5,再忽略前2个,取后3个元素输出。

Stream.iterate(1, i -> i + 1).limit(5).skip(2).forEach(System.out::println);
5、
Stream<T> distinct()

没啥说的,去重用的。
例如:
IntStream.of(1,1,3,4,5,6,4).distinct().forEach(System.out::println);
返回1,3,4,5,6

6 、
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
求最大最小值,一般而言,如果是数字可用IntStream,IntStream中的求最大值,最小值是不带参数的,max(),min(),另外还提供了一个sum()求和方法。
如果IntStream是一个空对象,那么sum可以返回0,而min,max是一个null , 所以这里sum的返回值用的是int,而min和max返回值是OptionalInt,用于规避值为null的情况,所以一般可以这么写.

IntStream.of(1,2,3,4,5).max().ifPresent(System.out::println);
针对于IntStream的统计方法,它还额外提供了一个方法:

IntSummaryStatistics summaryStatistics()

IntSummaryStatistics 这个类,它也提供了数值统计的常用方法,如count,sum,min,max ,average等

例如:

IntSummaryStatistics intSummaryStatistics = IntStream.of(1, 2, 3, 4, 5).summaryStatistics();
System.out.println(intSummaryStatistics.getMax());
System.out.println(intSummaryStatistics.getMin());
System.out.println(intSummaryStatistics.getAverage());

 

 例子请看这里:https://github.com/LeeScofield/java8

以上是关于Java 8 Stream介绍及使用的主要内容,如果未能解决你的问题,请参考以下文章

学习Java 8 Stream Api - Stream 周边及其他

Java 8中用法优雅的Stream,性能也"优雅"吗?

Java lamda和predicate用法例子

Java lamda和predicate用法例子

Java 8 Stream 流 - 使用

Java—Java 8 新增特性详解(Predicate和Stream)