文章目录
概述
Java8的一个新特性就是增加了Stream API,这东西提供了一个对数据更加便捷的一些操作。
首先就是,相关的类在java.util.stream
包里面。其次,流是一种渠道,用来处理对应数据源的的各种元素。
需要注意的是:1、Stream本身不存储元素,2、Stream不会改变原有的数据源,而是产生新的数据。3、Stream的各种操作不是立即进行的,而是在调用的时候才会执行。
最后,Stream操作有以下三个步骤:
- 创建Stream:根据相应的数据源产生需要的Stream。
- 中间操作:进行各种操作,处理数据。
- 终止操作:执行中间操作链,产生相应的结果。
创建Stream
创建Stream有以下的几种方式:
- 通过Collection接口新加入的方法,由列表创建一个流
- Arrays的静态方法stream()创建一个来自数组的流
- 通过Stream.of()方法传入一个值,创建对应的流
- 由Stream.iterate()和Stream.generate()创建无限流
接下来就说一下这几种方法是怎么用的
Collection的新方法
Collection接口加入了以下两个新方法,用来创建不同的流
- stream():返回一个顺序流
- parallelStream():返回一个并行流
然后创建起来很简单:
List<Integer> list = new ArrayList<>();
Stream<Integer> stream1 = list.stream();
Stream<Integer> parallelStream1 = list.parallelStream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Stream<String> parallelStream2 = set.parallelStream();
Arrays.stream()
这个方法是通过数组创建对应的流
double[] doubles = new double[21];
DoubleStream stream1 = Arrays.stream(doubles);
int[] ints = new int[32];
IntStream stream2 = Arrays.stream(ints);
String[] strings = new String[12];
Stream<String> stream3 = Arrays.stream(strings);
其中DoubleStream
和IntStream
是Stream
的一个实现类,专门用来处理对应的数据的,另外还有一个长整型,也有对应的实现类。
Stream.of()
这个方法的参数是可变的,我们可以传入一个及以上的参数来创建流。
另外就是注意一下流的泛型,别出错了。
int a = 23;
double b = 23.2323;
long c = 5465;
String d = "ass";
Stream<Object> a1 = Stream.of(a, b, c, d);
Stream.iterate()和Stream.generate()
这两个方法创建出来的都是无限流,就是如果不加以限制,这个流是无穷无尽的,会一直进行下去。
一般来说这两个方法都会对应limit()使用,不然就一直不停了。
然后接下来说这个是咋用的。
iterate()
这个方法有两个参数,第一个是流创建的基准,后面的是根据前一个值,然后生成下一个值,可以看作是递推公式。
Stream<Integer> stream1 = Stream.iterate(33, (x) -> x + 23);
Stream<String> stream2 = Stream.iterate("as", (x) -> x + "cc");
generate()
这个就是直接根据传入的方法创建对象,参数是一个实现Supplier接口的对象,可以使用Lambda表达式。
Stream<Object> stream = Stream.generate(Object::new);
中间操作
中间操才是流的真正精华,这个就可以看作是一个流水线,然后对数据进行各种处理。
需要注意的是,这些中间操作并不是在原有的流基础上进行修改,而是生成新的流。
以下就是各种方法,不想看我的这个的可以直接去看API文档。
- filter:过滤
- limit:取前几
- skip:跳过前几
- distinct:去重
- map:映射
- flatMap:
- sorted:排序
- peek:
filter
这个方法就是根据传入的方法,判断这个数据是否要留下来。
其中传入的参数是一个实现了Predicate
接口的对象,同样在这里可以使用Lambda表达式。
使用起来就如下:
private User[] users = {
new User(1, "陈二", 43, "女"),
new User(1, "张三", 23, "男"),
new User(1, "李四", 33, "男"),
new User(1, "王五", 13, "女"),
new User(1, "赵六", 51, "男")
};
@Test
public void test1() {
Stream<User> userStream = Arrays.stream(users);
userStream.filter((u) -> u.getAge() > 20)
.forEach( System.out :: println );
}
这个是过滤掉年龄不足20的人。
limit
这个就是在限制原有的数据,只输出前N个,具体的多少,要看传入的参数。
另外就是在取够了数据之后会直接结束,并不会将所有的数据都进行处理。
Stream<User> userStream = Arrays.stream(users);
userStream.filter((u) -> u.getAge() > 20)
.limit(2)
.forEach( System.out :: println );
在上面的基础上,限制了只取前两名。
skip
这个和上面的数据类似,不过是跳过前n个数据,另外就是如果流中本身的数据并不够n个,返回的是空流。
用这个的时候需要注意和前面方法的位置,每次都是在新流的基础上进行处理。 例如我在limit(2)
后面使用.skip(2)
那输出永远都是空,原因就是在limit(2)
处理之后的新流之中就只有两个元素了,再跳过两个之后就直接成空的了。其他的也类似。
这个是在过滤之后的结果中跳过前两个,然后再只输出两个。
Stream<User> userStream = Arrays.stream(users);
userStream.filter((u) -> u.getAge() > 20)
.skip(2)
.limit(2)
.forEach( System.out :: println );
distinct
这个方法是对输入的流中的元素进行一个去重的操作,去重的依据是equals()
方法,所以用这个对自定义类型去重的时候记得重写方法,不然没用,因为这样对比的是内存中的地址,不同对象的地址当然不一样。
map
这个就是做一个映射操作,原本的元素,通过输入的函数进行各种处理,最后得到结果。
其输入的是一个Function
函数,同样可以使用Lambda表达式。
下面是一个小栗子,这个玩意就是将原本的User对象中的name取出来。
Stream<User> userStream = Arrays.stream(users);
userStream.map(User::getName)
.forEach(System.out :: println);
flatMap
这个大致勇用法同map相同,但是如果映射函数产生了多个流,会将这些流拼接起来,然后得到一个新的流。
这是一个栗子,然后就是依次输出每个User对象的name、age、sex属性。
Stream<User> userStream = Arrays.stream(users);
userStream.flatMap(u -> Stream.of(u.getName(), u.getAge(), u.getSex() ))
.forEach(System.out :: println);
sorted
没啥好说的,就是排序。
这个方法有两个重载函数,一个没有参数,一个有参数。
- Stream sorted()
这个虽然不需要传入参数,但是需要流中的元素都是实现了Comparable
接口的类型,然后怎么排序就看是怎么实现其中的compareTo
方法了。如果不实现这个接口,没有提示,在运行的时候会报错。 - Stream sorted(Comparator<? super T> comparator)
这个就需要传入一个自定义的比较器了,好处就是不需要实现接口。
下面是一个栗子,这个会将User对象安装年龄升序排序。
Stream<User> userStream = Arrays.stream(users);
userStream.sorted(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return Integer.compare(o1.getAge(), o2.getAge());
}
}).forEach(System.out :: println);
peek
这个方法就是在元素输出的时候执行这个方法中的函数进行处理,注意是在每个元素输出之前才会执行这个方法。
这个栗子对每个元素会连续输出条数据。
Stream<User> userStream = Arrays.stream(users);
userStream.peek(System.out :: println)
.forEach(System.out :: println);
终止操作
终止操作,就是流进行到这里就到头了,接下来就不能进行操作了。
然后大致有以下的各种方法:
- forEach:遍历流中的数据
- allMatch:检查是否匹配所有元素
- anyMatch:检查是否至少匹配一个元素
- noneMatch:检查是否没有匹配的元素
- findFirst:返回第一个元素
- findAny:返回当前流中的任意元素
- count:返回流中元素的总个数
- max:返回流中最大值
- min:返回流中最小值
forEach
这个通常用来输出,但是不知是可以用来输出,传入的参数为一个实现了Consumer
接口的对象。
没有栗子,上面的基本上都是用这个进行输出的。
allMatch
判断流中的元素是否全部符合条件,具体的判断条件由传入的函数判断。
这个函数的参数为实现了Predicate
接口的对象,由这个对象判断是否符合条件。
这个栗子判断流中的对象,性别是否为男。
System.out.println(userStream.allMatch((u) -> "男".equals(u.getName())));
anyMatch
同上面的相同,但是判断的是是否存在匹配的元素,也就是只要有一个就行了。
noneMatch
这个检查是否存在不匹配的元素,有一个不匹配就返回true。
findFirst
这个得到流中的第一个元素
System.out.println(userStream.findFirst());
需要注意的是,这个方法的返回类型并不是流中原有的数据,而是一个Optional<T>
类型,这个东西也是在Java8中刚出来的。
具体就对原本的数据进行了一次封装,对空指针异常的情况进行了一些处理。
findAny
得到流中的任意一个元素,大致同上。
count
获取流中有多少个元素,返回值是长整型的。
System.out.println(userStream.count());
max
获取流中最大的元素,具体的怎么是最大的,需要根据你传入的参数进行判断。
传入的是实现了Comparator
接口的对象。
System.out.println(userStream.max(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return Integer.compare(o1.getAge(), o2.getAge());
}
}));
min
获取最小的元素,用法类似上面。