Stream简介
Stream 使用一种类似用 SQL 语句从数据库查询数据的方式来提供一种对 Java 集合的运算和表达。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
Stream(流)是一个来自数据源的元素队列并支持聚合操作。
元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
聚合操作类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。
以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
Stream对象的获取方式
这里简单介绍常用的3种获取流对象的方式
构建一个pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
class Dog {
private String name;
private Integer age;
private Integer health;
/**
* 下文中list数据均从此方法拿到
* @return list集合
*/
public static List<Dog> getDogList() {
List<Dog> dogList = new ArrayList();
dogList.add(new Dog("aaa", 0, 100));
dogList.add(new Dog("bbb", 1, 80));
dogList.add(new Dog("ccc", 2, 100));
dogList.add(new Dog("ddd", 3, 70));
dogList.add(new Dog("eee", 4, 100));
dogList.add(new Dog("abb", 5, 90));
dogList.add(new Dog("acc", 6, 100));
dogList.add(new Dog("add", 7, 70));
dogList.add(new Dog("aee", 8, 20));
return dogList;
}
1.通过Collection集合来获取
stream():返回的是串行流即集合内元素顺序不变
parallelStream():返回的是并行流即集合内元素顺序随机
开发者实例
/**
* 通过集合获取Stream对象
*/
@Test
public void test01() {
//获取List集合
List<Dog> dogList = Dog.getDogList();
//获取Stream(串行/并行)对象
Stream<Dog> dogStream = dogList.stream();
Stream<Dog> dogStream = dogList.parallelStream();
}
2.通过Arrays数组工具类来获取
开发者实例
/**
* 通过数组工具类获取Stream对象
*/
@Test
public void test02() {
//通过不同类型的数组获取Stream对象
IntStream intStream = Arrays.stream(new int[]{1, 2, 3, 4, 5, 6, 7, 8});
Stream<Dog> dogStream = Arrays.stream(new Dog[]{new Dog("DaHuang", 9,100), new Dog("XiaoHei", 5,100)});
}
3.通过Stream类来获取
开发者实例
/**
* 通过Stream的of(T... values)获取Stream对象
*/
@Test
public void test03() {
//通过不同类型的数组获取Stream对象
Stream<Dog> dogStream = Stream.of(new Dog("DaHuang", 9,100), new Dog("XiaoHei", 5,100));
Stream<Integer> integerStream = Stream.of(1, 2, 3);
}
常用API
上文讲述到流的操作包含中间操作和最终操作,中间操作结束总是返还一个流对象,只有经过最终操作才可以得到最终的结果,最终操作返还的类型不一定,根据API来决定。
中间操作:filter,disinct,map,skip,limit,sorted等
最终操作:forEach,allMatch,noneMatch,anyMatch,count,max,min,reduce,collect等
迭代(forEach)
用来迭代流中的每个数据。
开发者实例
@Test
public void test04() {
Stream<Dog> stream = Dog.getDogList().stream();
//展示流中的数据
stream.forEach(System.out::println);
}
过滤(filter)
用于通过设置的条件过滤出元素
开发实者例
@Test
public void test04() {
Stream<Dog> stream = Dog.getDogList().stream();
//筛选出狗狗年龄大于5且名字中包含a的狗狗信息
stream.filter(dog -> dog.getAge() > 5 && dog.getName().contains("a")).forEach(System.out::println);
}
筛选(distinct)
用于去掉重复的元素。
开发者实例
@Test
public void test05() {
List<Dog> dogList = Dog.getDogList();
//添加重复元素
dogList.add(new Dog("aee", 8, 20));
dogList.add(new Dog("aee", 8, 20));
Stream<Dog> stream = dogList.stream();
//筛选,比较流中元素的equals()和hashCode()去掉重复元素
stream.distinct().forEach(System.out::println);
}
映射(map)
用于映射每个元素到对应的结果。
开发者实例
@Test
public void test05() {
Stream<Dog> stream = Dog.getDogList().stream();
//只显示狗狗的姓名
stream.map(dog -> dog.getName()).forEach(System.out::println);
}
跳过(skip)
用于跳过流中指定的元素个数,如果流中元素不足要跳过的个数,则返回一个空流。
开发者实例
@Test
public void test07() {
Stream<Dog> stream = Dog.getDogList().stream();
//跳过元素,跳过流中前2个元素 。
stream.skip(2).forEach(System.out::println);
}
截断(limit)
用于获取流中指定的元素个数。
开发者实例
@Test
public void test08() {
Stream<Dog> stream = Dog.getDogList().stream();
//获取流中的前5个元素。
stream.limit(5).forEach(System.out::println);
}
排序(sorted)
sorted():自然排序,升序
sorted(Comparator<? super T>):定制排序
自然排序
sorted()
开发者实例
@Test
public void test09() {
Stream<Integer> intStream = Stream.of(1, 5, 9, 7, -48, 0, 85, 3, 5);
//自然排序并打印
intStream.sorted().forEach(System.out::println);
}
这里注意如果是文中Dog这种类型使用自然排序需要实现Comparable接口,重写内部排序方法的规则,否则会抛出异常。
异常案例
@Test
public void test10() {
Stream<Dog> stream = Dog.getDogList().stream();
//自然排序Dog对象
stream.sorted().forEach(System.out::println);
}
/*
java.lang.ClassCastException: class com.wenbin.java21.Dog cannot be cast to class java.lang.Comparable......
*/
这里运行抛出异常,思考一下,如果不调用forEach()的话代码是否还可以正常运行?
答案是可以,因为文章一开始讲到,流毕竟要有终止操作才可以结束得到结果,如果只经过了中间操作,元素始终都在流中。
如果想对对象实现排序并返还那么就需要用到定制排序。
定制排序
sorted(Comparator<? super T>)
开发者实例
@Test
public void test11() {
Stream<Dog> stream = Dog.getDogList().stream();
//定制排序Dog对象
stream.sorted((a, b) -> {
//按年龄排序升序
int compare = Integer.compare(a.getAge(), b.getAge());
//如果年龄相同则比较健康值
if (compare == 0) {
return Integer.compare(a.getHealth(), b.getHealth());
}
//返回比较结果
return compare;
}).forEach(System.out::println);
}
上文中Integer类的compare(int x, int y)方法
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
匹配(allMatch/noneMatch/anyMatch)
allMatch(Predicate<? super T>):检查是否匹配所有元素
anyMatch(Predicate<? super T>):检查是否匹配其中一个元素
noneMatch(Predicate<? super T>):检查是否没有匹配的元素
开发者实例
@Test
public void test12() {
Stream<Dog> stream = Dog.getDogList().stream();
//流中的每个元素中是否年龄都>=1
boolean result = stream.allMatch(d -> d.getAge() >= 1 );
}
@Test
public void test13() {
Stream<Dog> stream = Dog.getDogList().stream();
//流中的每个元素中是否有一个年龄>=9
boolean result = stream.anyMatch(d -> d.getAge() >= 9 );
}
@Test
public void test14() {
Stream<Dog> stream = Dog.getDogList().stream();
//流中的每个元素中是否年龄都没有>=9
boolean result = stream.noneMatch(d -> d.getAge() >= 9 );
}
查找(findFirst/findAny)
findFirst():返回第一个元素
findAny():返回任意一个元素
开发者实例
@Test
public void test15() {
Stream<Dog> stream = Dog.getDogList().parallelStream();
//返回第一个元素 与串行流无关 返回的是dogList中的的第一个
Optional<Dog> first = stream.findFirst();
}
@Test
public void test16() {
Stream<Dog> stream = Dog.getDogList().parallelStream();
//返回流中的任意元素 但是经过多次测试 返回的总是同一个元素
Optional<Dog> any = stream.findAny();
}
函数(count/max/min)
count():返回流中的元素总个数
max(Comparator<? super T>):返回流中元素的最大值
min(Comparator<? super T>):返回流中元素的最小值
开发者实例
@Test
public void test17() {
Stream<Dog> stream = Dog.getDogList().stream();
//返回流中元素的个数 空流则返回0
long count = stream.count();
}
@Test
public void test18() {
Stream<Dog> stream = Dog.getDogList().stream();
//返回流中年龄最大的对象
Optional<Dog> max = stream.max((a, b) -> {
return Integer.compare(a.getAge(), b.getAge());
});
}
@Test
public void test19() {
Stream<Dog> stream = Dog.getDogList().stream();
//返回流中最小的年龄
Optional<Integer> min =
stream.map(d -> d.getAge()).
min((a, b) -> Integer.compare(a, b));
}
归约(reduce)
reduce(BinaryOperator<T>):将流中的元素结合起来得到一个值
reduce(T,BinaryOperator<T>):将流中的元素结合起来得到一个值,每次结合时都会额外带上第一个参数T的值
开发者实例
@Test
public void test20() {
Stream<Dog> stream = Dog.getDogList().stream();
//返回所有狗狗的健康度总和
Optional<Integer> reduce = stream.map(Dog::getHealth)
.reduce((a, b) -> a + b);
//返回所有狗狗的名字总和
//Optional<String> reduce = stream.map(Dog::getName)
//.reduce((a, b) -> a + b);
}
@Test
public void test21() {
Stream<Dog> stream = Dog.getDogList().stream();
// //返回所有狗狗的健康度总和 参数中的10就是每次操作的初始值 共9次故结果是健康度总和+90
Integer reduce = stream.map(Dog::getHealth).reduce(10, (a, b) -> a + b);
}
收集(collect)
将流中的所有元素转化成其他形式
开发者实例
@Test
public void test22() {
Stream<Dog> stream = Dog.getDogList().stream();
//将流中的元素转化成list(元素有序)和set(元素无序)
//List<Dog> collect = stream.collect(Collectors.toList());
//Set<Dog> collect1 = stream.collect(Collectors.toSet());
//将流中的元素转化成map,键为狗狗的名字,值为对应的健康值
Map<String, Integer> collect = stream.collect(Collectors.toMap(d -> d.getName(), d -> d.getHealth()));
}
综合实例
获得一个List储存狗狗,List里面存储所有狗狗年龄和健康值符合(0-4岁健康值在100,其他岁数健康值在100-50以内的,每个年龄只储存一次,相同年龄则存储健康值最大的,健康值也相同以先储存的为准),List里按年龄从大到小储存。
要求所有操作都以流的API为来完成
这里为了效果明显,用到新的元素集合
@Data
@AllArgsConstructor
@NoArgsConstructor
class Dog {
private String name;
private Integer age;
private Integer health;
public static List<Dog> getDogList1() {
List<Dog> dogList = new ArrayList();
dogList.add(new Dog("0岁1号", 0, 100));
dogList.add(new Dog("0岁2号", 0, 20));
dogList.add(new Dog("1岁1号", 1, 80));
dogList.add(new Dog("1岁2号", 1, 50));
dogList.add(new Dog("2岁1号", 2, 100));
dogList.add(new Dog("2岁2号", 2, 90));
dogList.add(new Dog("3岁1号", 3, 70));
dogList.add(new Dog("3岁2号", 3, 100));
dogList.add(new Dog("4岁1号", 4, 100));
dogList.add(new Dog("5岁1号", 5, 90));
dogList.add(new Dog("5岁2号", 5, 40));
dogList.add(new Dog("5岁3号", 5, 100));
dogList.add(new Dog("6岁1号", 6, 50));
dogList.add(new Dog("6岁2号", 6, 100));
dogList.add(new Dog("7岁1号", 7, 70));
dogList.add(new Dog("7岁2号", 7, 30));
dogList.add(new Dog("8岁1号", 8, 20));
return dogList;
}
}
@Test
public void test23() {
//定义一个map用来存储元素的年龄和健康值
Map<Integer, Integer> map = new HashMap();
Dog.getDogList1()
//拿到所有元素流对象
.stream()
//先把所有的元素按年龄升序,年龄相同健康值升序排序 用于后面过滤来确定如果map中有该元素那么map里的健康值一定是最大的
.sorted((d1, d2) -> {
int compare = Integer.compare(d1.getAge(), d2.getAge());
if (compare == 0) {
return -Integer.compare(d1.getHealth(), d2.getHealth());
}
return compare;
})
//开始过滤 每个年龄只留下健康值最大的元素
.filter(d -> {
//判断当前元素是否在map中 如果在返回对应的健康值
Integer health = map.get(d.getAge());
//开始判断
if ((d.getAge() <= 4 && d.getAge() >= 0 && d.getHealth() == 100)||(d.getAge() > 4 && d.getHealth() >= 50 && d.getHealth() <= 100)) {
//判断当前元素的年龄是否在map里,如果不在map里面则 添加到里面
if (health == null) {
map.put(d.getAge(), d.getHealth());
return true;
}
//在里面直接过滤掉当前元素 因为相同年龄元素,是按健康值降序排序
return false;
}
return false;
})
//此时流中数据是按年龄逆序的 需要再次排序改为正序
.sorted((d1, d2) -> -Integer.compare(d1.getAge(), d2.getAge()))
//流的最终操作,将流对象转化为List集合
.collect(Collectors.toList())
//遍历该集合
.forEach(System.out::println);
}