Java 8 Stream
Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用Stream API无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的java.util.stream 是一个函数式语言+多核时代综合影响的产物。
@Test public void test() { // 通过集合的stream方法获取流 List<String> list = new ArrayList<>(); Stream<String> stream = list.stream(); // 通过Arrays工具类的stream方法获取流 Stream<String> stream1 = Arrays.stream(new String[]{"1", "2", "3", "4", "5"}); // 通过Stream类的of方法获取流 Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5); // 创建无限流 Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2); }
以上是四种获取Stream的方式,最后一种创建的是无限流,也就是说,该流创建的是从0开始,每次加2的无限序列。
使用Stream我们能够很轻松地实现过滤操作,比如获取无限流中前5个元素,代码如下:
@Test public void test() { Stream<Integer> stream = Stream.iterate(0, (x) -> x + 2); Stream<Integer> stream1 = stream.limit(5); stream1.forEach(System.out::println); }
通过limit方法即可实现获取前5个元素,这里使用了forEach方法进行遍历,并使用了Lambda表达式,我们一起来复习一下,查看forEach方法的源码:
void forEach(Consumer<? super T> action);
可以看到该方法的参数是一个消费型的函数式接口,其接口的抽象方法是带参而无返回值的,所以若是想输出元素,则可以如此做:
stream1.forEach((i) -> System.out.println(i));
又因为输出语句的参数和返回值与接口抽象方法的声明一致,所以可以使用方法引用,最终简化为:
stream1.forEach(System.out::println);
我们还可以通过Stream类的generate方法生成无限流:
@Test public void test() { Stream.generate(Math::random) .limit(5) .forEach(System.out::println); }
generate方法需要的是一个供给型接口,它的抽象方法是不带参而有返回值的,我们通过 Math.random() 方法生成随机数作为返回值,又因为random方法也是带一个参而有返回值的,所以可以使用类的静态方法引用,后续的Stream操作中将涉及大量的Lambda表达式,届时将不再过多介绍Lambda表达式的写法。
Stream的筛选操作
刚才我们通过limit方法简单地了解到Stream的筛选功能,当然了,Stream的筛选能力可远不止如此,这里介绍四种筛选方法:
-
filter
-
limit
-
skip
-
distinct
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 30, 0), new User("王五", 25, 1), new User("赵六", 42, 1) ); // 筛选出年龄大于25的用户 Stream<User> stream = userList.stream() .filter((user) -> user.getAge() > 25); stream.forEach(System.out::println); }
通过filter方法,我们能够实现很多的筛选功能,比如这里就可以筛选出年龄大于25的用户,filter方法的参数是一个断言型接口,接收一个参数,返回值为boolean类型,即:为true则满足筛选条件,为false则不满足。
limit方法我们已经使用过了,它用来截断Stream,使得Stream获取从开始位置到指定位置的元素内容。
第三个方法是skip:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 30, 0), new User("王五", 25, 1), new User("赵六", 42, 1) ); // 筛选出年龄大于25的用户 Stream<User> stream = userList.stream() .skip(2); stream.forEach(System.out::println); }
它与limit方法正好相反,skip会丢弃掉从开始位置到指定位置的元素内容,比如上面这段程序便会将 张三 和 李四 用户的信息丢掉。
最后一个方法是distinct:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 30, 0), new User("王五", 25, 1), new User("王五", 25, 1), new User("王五", 25, 1), new User("赵六", 42, 1) ); // 筛选出年龄大于25的用户 Stream<User> stream = userList.stream() .distinct(); stream.forEach(System.out::println); }
该方法用于去除流中重复的元素,需要注意的是该方法依赖于equals和hashCode方法,所以User对象必须重写这两个方法:
public class User { private String name; private Integer age; private Integer sex; ...... @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(name, user.name) && Objects.equals(age, user.age) && Objects.equals(sex, user.sex); } @Override public int hashCode() { return Objects.hash(name, age, sex); } }
Stream的映射操作
Stream中的map方法用于映射操作,它能将元素转换成其它形式或提取信息,接收一个函数作为参数,该函数会被应用到Stream中的每个元素上,并将其映射成为一个新的元素。
@Test public void test() { List<String> list = Arrays.asList("aa", "bb", "cc", "dd", "ee"); list.stream() .map(String::toUpperCase) .forEach(System.out::println); }
map方法的参数是一个函数型接口,Stream中的每个元素都会被应用到该接口方法的实现上,所以每个元素都会被转换成大写字母。
map方法也能用于提取Stream中每个元素的信息:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 30, 0), new User("王五", 25, 1), new User("赵六", 42, 1) ); userList.stream() .map(User::getName) .forEach(System.out::println); }
此时所有用户的名字就都被提取出来了。
Stream的排序操作
Stream中的排序也分为两种,自然排序和自定义排序,首先是自然排序,调用sorted方法即可实现:
@Test public void test() { List<Integer> list = Arrays.asList(3, 1, 6, 7, 5, 9); list.stream() .sorted() .forEach(System.out::println); }
自定义排序就需要传入自己实现的比较器:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("赵六", 42, 1) ); userList.stream() .sorted((u1, u2) -> { if (u1.getAge().equals(u2.getAge())) { return u1.getName().compareTo(u2.getName()); } else { return u1.getAge().compareTo(u2.getAge()); } }) .forEach(System.out::println); }
该程序段实现了按照年龄对User信息进行排序,若年龄相同,则再按照姓名排序。
Stream的匹配操作
Stream中提供了丰富的匹配方法用于校验数据,分别如下:
-
allMatch:是否匹配所有元素
-
anyMatch:是否至少匹配一个元素
-
noneMatch:是否没有匹配所有元素
-
findFirst:返回第一个元素
-
findAny:返回流中的任意元素
-
count:返回元素个数
-
max:返回最大值
-
min:返回最小值
比如:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("赵六", 42, 1) ); boolean flag = userList.stream() .allMatch((u) -> u.getSex().equals(1)); System.out.println(flag); }
这段程序中,它会判断Stream中的所有User对象的sex值是否为1,若满足,则返回true,否则返回false,这里的结果当然就是false了。
若是将匹配方法修改为 anyMatch ,则结果会变为true:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("赵六", 42, 1) ); boolean flag = userList.stream() .anyMatch((u) -> u.getSex().equals(1)); System.out.println(flag); }
当所有User对象的sex为0时结果才为false。
noneMatch方法:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("赵六", 42, 1) ); boolean flag = userList.stream() .noneMatch((u) -> u.getSex().equals(1)); System.out.println(flag); }
因为Stream中有User对象的sex值为1,所以没有匹配所有元素是不成立的,故结果为false,只有当所有User对象的sex值均为0,此时没有匹配所有元素成立,结果才为true。
findFirst方法:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("赵六", 42, 1) ); Optional<User> optional = userList.stream() .sorted(Comparator.comparing(User::getAge)) .findFirst(); User user = optional.get(); System.out.println(user); }
该程序段对Stream中的User对象按年龄进行升序,并使用findFirst方法获取第一个User对象,注意这里的返回值是Optional,这也是JDK1.8的新特性,它是用来避免频繁出现的空指针异常的,这个我们后面会介绍。
findAny方法:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("赵六", 42, 1) ); Optional<User> optional = userList.stream() .filter((u) -> u.getAge().equals(20)) .findAny(); User user = optional.get(); System.out.println(user); }
这里首先使用filter过滤出年龄为20的用户,此时张三和李四都符合条件,而findAny方法便会从这两个对象中随机选择一个返回,然而这种情况它只会一直返回姓名为张三的User对象,因为当前的Stream是串行流,我们需要获取并行流才能实现随机获取的效果:
// parallelStream()方法获取并行流 Optional<User> optional = userList.parallelStream() .filter((u) -> u.getAge().equals(20)) .findAny();
count方法:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("赵六", 42, 1) ); long count = userList.stream() .count(); System.out.println(count); }
获取当前Stream中的元素个数,结果为4。
max方法:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("赵六", 42, 1) ); Optional<User> optional = userList.stream() .max(Comparator.comparing(User::getAge)); User user = optional.get(); System.out.println(user); }
该程序段获取的是Stream中年龄最大的User对象。
min方法:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("赵六", 42, 1) ); Optional<User> optional = userList.stream() .min(Comparator.comparing(User::getAge)); User user = optional.get(); System.out.println(user); }
将调用方法换为min,则它将获取Stream中年龄最小的User对象。
Stream的收集操作
@Test public void test() { List<Integer> list = Arrays.asList(3, 1, 6, 7, 5, 9); Integer result = list.stream() .reduce(0, Integer::sum); System.out.println(result); }
该程序段中使用了一个新的方法:reduce ,它的作用是将流中的元素按规则反复地结合起来,得到一个值,比如这里的结果就是将流中的元素全部相加得到和,我们先将方法引用展开再说说其原理:
@Test public void test() { List<Integer> list = Arrays.asList(3, 1, 6, 7, 5, 9); Integer result = list.stream() .reduce(0, (i, j) -> i + j); System.out.println(result); }
reduce会将第一个参数值0作为起始值,其第一次迭代便是将0作为变量i的值,并从流中取出第一个元素作为变量j的值,所以第一次的执行结果为:0 + 1 = 1 ,并将该结果作为变量i的值,从流中取出第二个元素作为变量j的值进行下一次运算,结果为:1 + 3 = 4 ,以此类推,最终得到总和。
Stream提供了collect方法用于收集操作,它可以将那些经过过滤、映射、排序等操作后的Stream数据重新收集起来,成为一个新的集合数据,代码如下:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("赵六", 42, 1) ); List<String> nameList = userList.stream() .map(User::getName) .collect(Collectors.toList()); System.out.println(nameList); }
此时我们便将所有User对象的姓名取出,并收集到了一个新的集合中。Collectors类提供了非常多的静态方法供我们更方便地进行收集,toList、toSet、toMap、toCollection等等,比如将数据收集成Set集合:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("王五", 25, 1), new User("赵六", 42, 1) ); Set<String> nameSet = userList.stream() .map(User::getName) .collect(Collectors.toSet()); System.out.println(nameSet); }
则这样便取出了所有User对象的姓名且不重复。若是想将数据收集成HashSet呢?Collectors类中并未提供toHashSet方法,但我们可以通过toCollection方法实现:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("王五", 25, 1), new User("赵六", 42, 1) ); Set<String> nameSet = userList.stream() .map(User::getName) .collect(Collectors.toCollection(HashSet::new)); System.out.println(nameSet); }
toCollection方法需要接收一个供给型接口,通过构造器引用创建HashSet对象即可。
Collectors还提供了一些类似SQL的聚合操作,比如求元素个数:
Long count = userList.stream() .map(User::getName).count();
求平均值:
Double avgAge = userList.stream() .collect(Collectors.averagingDouble(User::getAge));
求总和:
Integer sumAge = userList.stream() .collect(Collectors.summingInt(User::getAge));
最大值:
Optional<User> optional = userList.stream() .collect(Collectors.maxBy(Comparator.comparingInt(User::getAge))); User user = optional.get();
最小值:
Optional<User> optional = userList.stream() .collect(Collectors.minBy(Comparator.comparingInt(User::getAge))); User user = optional.get();
它甚至能够实现分组,比如按照年龄分组:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("王五", 25, 1), new User("赵六", 42, 1) ); Map<Integer, List<User>> map = userList.stream() .collect(Collectors.groupingBy(User::getAge)); System.out.println(map); }
运行结果:
{20=[User{name='张三', age=20, sex=0}, User{name='李四', age=20, sex=0}], 25=[User{name='王五', age=25, sex=1}, User{name='王五', age=25, sex=1}], 42=[User{name='赵六', age=42, sex=1}]}
还能够进行多级分组,比如先按照年龄分组,再按照性别分组:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("王五", 25, 1), new User("赵六", 42, 1) ); Map<Integer, Map<String, List<User>>> map = userList.stream() .collect(Collectors.groupingBy(User::getAge, Collectors.groupingBy((u) -> { if (u.getSex() == 0) { return "男"; } else { return "女"; } }))); System.out.println(map); }
运行结果:
{20={男=[User{name='张三', age=20, sex=0}, User{name='李四', age=20, sex=0}]}, 25={女=[User{name='王五', age=25, sex=1}, User{name='王五', age=25, sex=1}]}, 42={女=[User{name='赵六', age=42, sex=1}]}}
按年龄进行分区:
@Test public void test() { List<User> userList = Arrays.asList( new User("张三", 20, 0), new User("李四", 20, 0), new User("王五", 25, 1), new User("王五", 25, 1), new User("赵六", 42, 1) ); Map<Boolean, List<User>> map = userList.stream() .collect(Collectors.partitioningBy((u) -> u.getAge() > 20)); System.out.println(map); }
运行结果:
{false=[User{name='张三', age=20, sex=0}, User{name='李四', age=20, sex=0}], true=[User{name='王五', age=25, sex=1}, User{name='王五', age=25, sex=1}, User{name='赵六', age=42, sex=1}]}
它会按照年龄大于20和小于等于20的规则将User对象分为两组。
-
之前看过一篇文章介绍H标签的使用方法:H1必须有只能是一个,H2标签可以是多个。不同标签的作用h1和h2标签主要是提高关键词的密度更容易让蜘蛛抓取我们首先要明白HEADER标签是什么。HEADER标签就是HTML语言中的h1到h6定义标题头的六个不同文字大小的TAGES。本质是为了呈现内容结构,共有
-
高并发下如何设计秒杀系统?秒杀系统是网络商家为了促销等目的进行的网上限时抢购活动。比如某宝某东某夕夕上的秒杀。用户在规定的时间内,定时定量的秒杀,无论商品是否秒杀完毕,该场次的秒杀活动都会结束。秒杀系统具有瞬时流量、高并发读、高并发写以及高可用等特点。秒杀时会有大量用户在同一时间进行抢购,瞬时并发访
-
经常做仓储管理系统,很少关注电商系统,之前兼职做过一个电商系统,其对应的商品档案就很容易和仓储管理系统混淆,今天抽个时间整理下他们在前端展示的区别和联系。一个电商系统,SPU、SKU、快照等设计的好坏,影响到后面的开发进度,以及架构的调整等。而SPU、SKU、快照又是一个电商系统的核心。SPU,是s
-
Java8新特性中最为重要的便是Lambda表达式和StreamAPI了,先来了解一下Lambda表达式吧。Lambda表达式Lambda表达式是一个匿名函数,我们可以将Lambda表达式理解为一段可以作为参数传递的代码,通过Lambda表达式,我们可以将Java程序变得更加简洁和灵活。来看一段程序
-
Java8中的Stream是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregateoperation),或者大批量数据操作(bulkdataoperation)。StreamAPI借助于同样新出现的Lambda表达式,极大的提高编程效率和
-
要养成一个好的编码习惯从自己编码开始,对自己代码的合理化命名,编码不仅对自己有好处,而且别人也容易读懂你的代码。所以下载阿里的代码规范插件来约束自己凌乱的代码。阿里规范插件GitHub地址:https://github.com/alibaba/p3cIDEA安装该插件步骤:1.打开IDEA,File