相信Java8的Stream 大家都已听说过了,但是可能大家不会用或者用的不熟,文章将带大家从零开始使用,循序渐进,带你走向Stream的巅峰。
操作符
什么是操作符呢?操作符就是对数据进行的一种处理工作,一道加工程序;就好像工厂的工人对流水线上的产品进行一道加工程序一样。
Stream的操作符大体上分为两种:中间操作符和终止操作符
中间操作符
对于数据流来说,中间操作符在执行制定处理程序后,数据流依然可以传递给下一级的操作符。
中间操作符包含8种(排除了parallel,sequential,这两个操作并不涉及到对数据流的加工操作):
- map(mapToInt,mapToLong,mapToDouble)
- 转换操作符,把比如A->B,这里默认提供了转int,long,double的操作符。
- flatmap(flatmapToInt,flatmapToLong,flatmapToDouble)
- 拍平操作比如把 int[]{2,3,4} 拍平 变成 2,3,4 也就是从原来的一个数据变成了3个数据,这里默认提供了拍平成int,long,double的操作符。
- limit
- 限流操作,比如数据流中有10个 我只要出前3个就可以使用。
- distint
- 去重操作,对重复元素去重,底层使用了equals方法。
- filter
- 过滤操作,把不想要的数据过滤。
- peek
- 挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等。
- skip
- 跳过操作,跳过某些元素。
- sorted(unordered)
- 排序操作,对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。
终止操作符
数据经过中间加工操作,就轮到终止操作符上场了;终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。
- collect
- 收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors。
- count
- 统计操作,统计最终的数据个数。
- findFirst、findAny
- 查找操作,查找第一个、查找任何一个 返回的类型为Optional。
- noneMatch、allMatch、anyMatch
- 匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。
- min、max
- 最值操作,需要自定义比较器,返回数据流中最大最小的值。
- reduce
- 规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。
- forEach、forEachOrdered
- 遍历操作,这里就是对最终的数据进行消费了。
- toArray
- 数组操作,将数据流的元素转换成数组。
代码演示
Stream 的一系列操作必须要使用终止操作,否者整个数据流是不会流动起来的,即处理操作不会执行。
map
可以看到 map 操作符要求输入一个Function的函数是接口实例,功能是将T类型转换成R类型的。
/**
* 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操作将原来的单词转换成了每个单词的长度,利用了String自身的length()方法,该方法返回类型为int。这里我直接使用了lambda表达式,关于lambda表达式。
public class Main {
public static void main(String[] args) {
Stream.of("apple","banana","orange","waltermaleon","grape")
.map(e->e.length()) //转成单词的长度 int
.forEach(e->System.out.println(e)); //输出
}
}
当然也可以这样,这里使用了成员函数引用,为了便于理解,后续的例子中将使用lambda表达式而非函数引用。
public class Main {
public static void main(String[] args) {
Stream.of("apple","banana","orange","waltermaleon","grape")
.map(String::length) //转成单词的长度 int
.forEach(System.out::println);
}
}
运行结果:
5
6
6
12
5
mapToInt
将数据流中得元素转成Int,这限定了转换的类型Int,最终产生的流为IntStream,及结果只能转化成int。
/**
* Returns an {@code IntStream} 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 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
*/
IntStream mapToInt(ToIntFunction<? super T> mapper);
public class Main {
public static void main(String[] args) {
Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.mapToInt(e -> e.length()) //转成int
.forEach(e -> System.out.println(e));
}
}
运行结果:
5
6
6
12
5
mapToLong、mapToDouble 与mapToInt 类似,只是类型不一样
flatmap
作用就是将元素拍平拍扁,将拍扁的元素重新组成Stream,并将这些Stream 串行合并成一条Stream
/**
* Returns a stream consisting of the results of replacing each element of
* this stream with the contents of a mapped stream produced by applying
* the provided mapping function to each element. Each mapped stream is
* {@link java.util.stream.BaseStream#close() closed} after its contents
* have been placed into this stream. (If a mapped stream is {@code null}
* an empty stream is used, instead.)
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @apiNote
* The {@code flatMap()} operation has the effect of applying a one-to-many
* transformation to the elements of the stream, and then flattening the
* resulting elements into a new stream.
*
* <p><b>Examples.</b>
*
* <p>If {@code orders} is a stream of purchase orders, and each purchase
* order contains a collection of line items, then the following produces a
* stream containing all the line items in all the orders:
* <pre>{@code
* orders.flatMap(order -> order.getLineItems().stream())...
* }</pre>
*
* <p>If {@code path} is the path to a file, then the following produces a
* stream of the {@code words} contained in that file:
* <pre>{@code
* Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8);
* Stream<String> words = lines.flatMap(line -> Stream.of(line.split(" +")));
* }</pre>
* The {@code mapper} function passed to {@code flatMap} splits a line,
* using a simple regular expression, into an array of words, and then
* creates a stream of words from that array.
*
* @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 which produces a stream
* of new values
* @return the new stream
*/
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
public class Main {
public static void main(String[] args) {
Stream.of("a-b-c-d","e-f-i-g-h")
.flatMap(e->Stream.of(e.split("-")))
.forEach(e->System.out.println(e));
}
}
运行结果:
a
b
c
d
e
f
i
g
h
flatmapToInt、flatmapToLong、flatmapToDouble 跟flatMap 都类似的,只是类型被限定了。
limit
限制元素的个数,只需传入 long 类型 表示限制的最大数
public class Main {
public static void main(String[] args) {
Stream.of(1,2,3,4,5,6)
.limit(3) //限制三个
//将输出 前三个 1,2,3
.forEach(e->System.out.println(e));
}
}
运行结果:
1
2
3
distinct
去除重复数据
public class Main {
public static void main(String[] args) {
Stream.of(1,2,3,1,2,4,3,2,1,0,0,1,2,3,1)
.distinct() //去重
.forEach(e->System.out.println(e));
}
}
运行结果:
1
2
3
0
filter
对某些元素进行过滤,不符合筛选条件的将无法进入流的下游
public class Main {
public static void main(String[] args) {
Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)
.filter(e->e>=5) //过滤小于5的
.forEach(e->System.out.println(e));
}
}
运行结果:
5
6
7
8
peek
挑选 ,将元素挑选出来,可以理解为提前消费
public class Main {
public static void main(String[] args) {
User w = new User("w",10);
User x = new User("x",11);
User y = new User("y",12);
Stream.of(w,x,y)
.peek(e->{e.setName(e.getAge()+e.getName());}) //重新设置名字 变成 年龄+名字
.forEach(e->System.out.println(e.toString()));
}
@Data
static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
}
运行结果:
demo.User(name=10w, age=10)
demo.User(name=11x, age=11)
demo.User(name=12y, age=12)
skip
跳过 元素
public class Main {
public static void main(String[] args) {
Stream.of(1,2,3,4,5,6,7,8,9)
.skip(4) //跳过前四个
.forEach(e->System.out.println(e)); //输出的结果应该只有5,6,7,8,9
}
}
运行结果:
5
6
7
8
9
sorted
排序 底层依赖Comparable 实现,也可以提供自定义比较器
这里Integer 实现了比较器
public class Main {
public static void main(String[] args) {
Stream.of(2,6,4,6,0)
.sorted()
.forEach(e->System.out.println(e));
}
}
运行结果:
0
2
4
6
6
这里使用自定义比较,当然User 可以实现Comparable 接口
public class Main {
public static void main(String[] args) {
User x = new User("x",11);
User y = new User("y",12);
User w = new User("w",10);
Stream.of(w,x,y)
.sorted((e1,e2)-> Integer.compare(e1.age, e2.age))
.forEach(e->System.out.println(e.toString()));
}
@Data
static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
}
运行结果:
demo.User(name=w, age=10)
demo.User(name=x, age=11)
demo.User(name=y, age=12)
collect
收集,使用系统提供的收集器可以将最终的数据流收集到List,Set,Map等容器中。
这里我使用collect 将元素收集到一个set中
public class Main {
public static void main(String[] args) {
Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.collect(Collectors.toSet()) //set 容器
.forEach(e -> System.out.println(e));
}
}
咦?,不是说终止操作符只能使用一次吗,为什么这里调用了forEach 呢?forEach不仅仅是是 Stream 中得操作符还是各种集合中得一个语法糖
public class Main {
public static void main(String[] args) {
Set<String> stringSet = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.collect(Collectors.toSet()); //收集的结果就是set
stringSet.forEach(e->System.out.println(e)); set的语法糖forEach
}
运行结果:
banana
orange
apple
waltermaleon
grape
count
统计数据流中的元素个数,返回的是long 类型
public class Main {
public static void main(String[] args) {
long count = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.count();
System.out.println(count);
}
}
运行结果:
5
findFirst
获取流中的第一个元素
这里找到第一个元素 apple
public class FindFirst {
public static void main(String[] args) {
Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.findFirst();
stringOptional.ifPresent(e->System.out.println(e));
}
}
运行结果:
apple
findAny 获取流中任意一个元素
public class FindAny {
public static void main(String[] args) {
Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
.parallel()
.findAny(); //在并行流下每次返回的结果可能一样也可能不一样
stringOptional.ifPresent(e->System.out.println(e));
}
}
noneMatch
数据流中得没有一个元素与条件匹配的
这里的作用是是判断数据流中一个都没有与aa 相等元素 ,但是流中存在 aa ,所以最终结果应该是false
public class NoneMatch {
public static void main(String[] args) {
boolean result = Stream.of("aa","bb","cc","aa")
.noneMatch(e->e.equals("aa"));
System.out.println(result);
}
}
运行结果:
false
allMatch和anyMatch 一个是全匹配,一个是任意匹配 和noneMatch 类似。
min
最小的一个,传入比较器,也可能没有(如果数据流为空)
public static void main(String[] args) {
Optional<Integer> integerOptional = Stream.of(0,9,8,4,5,6,-1)
.min((e1,e2)->e1.compareTo(e2));
integerOptional.ifPresent(e->System.out.println(e));
}
运行结果:
-1
max
元素中最大的,需要传入比较器,也可能没有(流为Empty时)
public static void main(String[] args) {
Optional<Integer> integerOptional = Stream.of(0,9,8,4,5,6,-1)
.max((e1,e2)->e1.compareTo(e2));
integerOptional.ifPresent(e->System.out.println(e));
}
运行结果:
9
reduce
是一个规约操作,所有的元素归约成一个,比如对所有元素求和,乘啊等。
这里实现了一个加法,指定了初始化的值
public class Main {
public static void main(String[] args) {
int sum = Stream.of(0,9,8,4,5,6,-1)
.reduce(0,(e1,e2)->e1+e2);
System.out.println(sum);
}
}
运行结果:
31
forEach
对每个数据遍历迭代
forEachOrdered
适用用于并行流的情况下进行迭代,能保证迭代的有序性
这里通过并行的方式输出数字
public class ForEachOrdered {
public static void main(String[] args) {
Stream.of(0,2,6,5,4,9,8,-1)
.parallel()
.forEachOrdered(e->{
System.out.println(Thread.currentThread().getName()+": "+e);});
}
}
运行结果:
ForkJoinPool.commonPool-worker-6: 0
ForkJoinPool.commonPool-worker-11: 2
ForkJoinPool.commonPool-worker-11: 6
ForkJoinPool.commonPool-worker-11: 5
ForkJoinPool.commonPool-worker-11: 4
ForkJoinPool.commonPool-worker-11: 9
ForkJoinPool.commonPool-worker-11: 8
ForkJoinPool.commonPool-worker-11: -1
toArray
转成数组,可以提供自定义数组生成器
public class ToArray {
public static void main(String[] args) {
Object[] objects=Stream.of(0,2,6,5,4,9,8,-1)
.toArray();
for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]);
}
}
}
运行结果:
0
2
6
5
4
9
8
-1
常用stream流操作
取出某个实体类某个属性为集合
List<String> streamList = list.stream().map(DictEntity::getName).collect(Collectors.toList());
取出某个实体类某个属性数组
Long[] ids = list.stream().map(DictEntity::getId).toArray(Long[]::new);
集合去重
list.stream().distinct().collect(Collectors.toList());
求和 int、double、long:
double max = list.stream().mapToDouble(User::getHeight).sum();
求平均
double average = list.stream().collect(Collectors.averagingDouble(Double::doubleValue));