背景:当使用集合数组的时候,经常会使用循环的方式逐个处理。在Java8提供了很多新的特性包括Stream,使得代码更简洁、偏声明式的编码风格,更容易体现出代码的逻辑意图,逻辑间解耦,一个stream中间处理逻辑,无需关注上游与下游的内容,只需要按约定实现自身逻辑即可
函数式接口
众所周知,Java8提供了很多新的特性,Lambda表达式,函数式接口,Optional,新的日期类api。今天简单聊一下Stream的前世今生。
Lambda表达式我们现在已经用的很多了,而函数式接口则是为了支持Lambda表达式,Java8提供了很多内置的函数式接口,如Runnable,Comparator等是从原有的API升级来的,而有些是Java8新增的,如Consumer等。
@FunctionalInterface public interface Runnable { public abstract void run(); }
类上有注解@FunctionalInterface就可以认为这是一个函数式接口,可以用在Lambda表达式中。Lambda表达式极大的简化了我们的编程
// jdk1.8之前 new Thread(new Runnable() { @Override public void run() { System.out.println("yes"); } }).start(); // jdk1.8及以后 new Thread(() -> System.out.println("yes")).start();
为了方便我们使用Lambda表达式,Java8提供了一些内置的函数式接口
函数式接口 | 方法 | 用途 |
Consumer 消费型接口 | void accept(T t) | 输入参数为T,没有返回 |
Supplier 供给型接口 | T get() | 返回R |
Function<T, R> 函数型接口 | R apply(T t) | 输入参数为T,返回为R |
Predicate 判断型接口 | boolean test(T t) | 对象是否满足条件,true为满足,false为不满足 |
Java8为什么要新提供这些函数式接口呢?
我举个例子你就明白了。
public class NumberClass{ Integer a, b, c; public NumberClass(final Integer a, final Integer b, final Integer c) { this.a = a; this.b = b; this.c = c; } public Integer getA() { return this.a; } public Integer getB() { return this.b; } public Integer getC() { return this.c; } }
我写了个1个方法
public static Integer doV1(NumberClass numberClass){ Integer result = 0; //step 1 result = (numberClass.getA() + numberClass.getB() + numberClass.getC()); //step 2 result = (result * numberClass.getA()); return result; }
结果1个方法还不够用,业务逻辑需要变动,但是原来的方法不能动,怎么办,再加个方法
public static Boolean doV2(NumberClass numberClass){ Integer result = 0; //step 1 result = (numberClass.getA() * numberClass.getB() + numberClass.getC()); //step 2 result = (result * numberClass.getA()); return result > 100; }
结果有需要加一些新功能,原来业务不变动~~~ 开始操蛋起来了,总是加方法也不是办法对吧,况且是功能性差不多的方法
直接优化一下写一个模版优化一下
public static <D> D processTemplate(NumberClass numberClass, Function<NumberClass, D> function){ D applyResult = function.apply(numberClass); return applyResult; }
NumberClass numberClass = new NumberClass(1, 2, 50); Integer integerResult = processTemplate(numberClass, (n) -> { Integer result = 0; //step 1 result = (numberClass.getA() + numberClass.getB() + numberClass.getC()); //step 2 result = (result * numberClass.getA()); return result; }); Boolean booleanResult = processTemplate(numberClass, (n) -> { Integer result = 0; //step 1 result = (numberClass.getA() * numberClass.getB() + numberClass.getC()); //step 2 result = (result * numberClass.getA()); return result > 100; });
上面的需求就可以用如下几行代码实现。
函数式接口的使用
函数式接口 | 方法 | 用途 |
Consumer 消费型接口 | void accept(T t) | 输入参数为T,没有返回 |
Supplier 供给型接口 | T get() | 返回R |
Function<T, R> 函数型接口 | R apply(T t) | 输入参数为T,返回为R |
Predicate 判断型接口 | boolean test(T t) | 对象是否满足条件,true为满足,false为不满足 |
@Test public void testCase1() { // 10 consumeTask(10, (m) -> System.out.println(m)); } public void consumeTask(int num, Consumer<Integer> consumer) { consumer.accept(num); } @Test public void testCase2() { // AAA System.out.println(strHandler("aaa", (str) -> str.toUpperCase())); } public String strHandler(String str, Function<String, String> function) { return function.apply(str); }
当然,为了方便我们的使用,还有很多其他的内置接口,看入参和返回值就能知道接口的作用
函数式接口 | 方法 |
BiFunction<T, U, R> | R apply(T t, U u) |
BiConsumer<T, U> | void accept(T t, U u) |
ToIntFunction | int applyAsInt(T value) |
IntFunction | R apply(int value) |
Stream介绍
在Java8之前,如果我们想对集合进行操作还是比较麻烦的。Java8设计了Stream API来简化对集合的操作,Stream API的设计基于函数式编程和lambda表达式,行云流水似的编程方式给人带来新的体验。
Stream操作分为如下三个步骤
创建Stream:从数据源,例如集合,数组中获取一个流
中间操作:对数据进行处理
终止操作:执行中间操作,并产生结果。一般返回void或一个非流的结果
注意当不执行终止操作的时候,中间操作不会执行
List<Integer> dataList = Arrays.asList(1, 2, 3, 4); // 没有输出 dataList.stream().map(x -> { System.out.println(x); return x;}); // 输出 1 2 3 4 // 正常是换行,我这用空格代替了,下同 dataList = dataList.stream().map(x -> { System.out.println(x); return x; }).collect(Collectors.toList());
创建Stream
// 1. Collection集合的stream()或者parallelStream() List<String> list = Lists.newArrayList(); Stream<String> stream1 = list.stream(); // 2. 调用Arrays.stream(T[] array)静态方法 Integer[] array = {1, 2, 3}; Stream<Integer> stream2 = Arrays.stream(array); // 3. 调用Stream.of(T... values)静态方法 Stream<String> stream3 = Stream.of("aa", "bb", "cc"); // 4. 调用Stream.iterate(final T seed, final UnaryOperator<T> f),创建无限流 // (x) -> x + 2 为函数式接口,传入x返回x+2,0为最开始的值 Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2); // 一直输出 0 2 4 6 8 10 12 ... stream4.forEach(System.out::println); // 5. 调用调用Stream.generate(),创建无限流 Stream<Integer> stream5 = Stream.generate(() -> 10); // 一直输出10,你可以用Random等类随机生成哈 stream5.forEach(System.out::println);
中间操作
函数名 | 解释 |
filter | 从流中排除某些元素 |
limit | 使元素不超过指定数量 |
skip |
跳过前n个元素,如果流中元素不超过n个,则返回一个空流 |
distinct | 通过hashCode()和equals()去除重复元素 |
List<Integer> list = Arrays.asList(1, 2, 3, 4); // 1 3 list.stream().filter(x -> x % 2 == 1).forEach(System.out::println); // 3 4 list.stream().skip(2).forEach(System.out::println);
看一下filter方法和forEach方法的定义
Stream.java
Stream<T> filter(Predicate<? super T> predicate); void forEach(Consumer<? super T> action);
这不就是我门上面介绍的函数式接口吗?
很多方法的入参其实就是一个函数式接口
函数名 | 解释 |
map | 接收一个函数作为参数,该函数被应用到每个元素上,并将其映射成一个新的元素 |
flatMap | 接受一个函数作为参数,将流中的每一个值都转换成另一个流,然后将所有流连接成一个流 |
先看这2个方法的定义
<R> Stream<R> map(Function<? super T, ? extends R> mapper); <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
map方法的入参和返回值可以为任意值
flatMap方法的入参为任意值,返回值必须为Stream
List<String> list = Arrays.asList("abcd", "efgh"); // [Ljava.lang.String;@7b3300e5 [Ljava.lang.String;@2e5c649 list.stream().map(x -> x.split("")).forEach(System.out::println); // a b c d e f g h list.stream().flatMap(x -> Arrays.stream(x.split(""))).forEach(System.out::println);
解释一下这个输出,x.split(“”)后为数组,所以第一个输出的为数组的地址
第二个x.split(“”)后为数组,然后将数组转为多个流,将多个流合并后输出
函数名 | 解释 |
sorted() | 自然排序,通过Comparable接口定义的规则来排序 |
sorted(Comparator) | 定制排序,通过Comparator接口定义的规则来排序 |
List<String> list = Arrays.asList("b", "a", "c"); // a b c list.stream().sorted().forEach(System.out::println); // c b a list.stream().sorted((x, y) -> y.compareTo(x)).forEach(System.out::println);
终止操作
函数名 | 解释 |
allMatch | 是否匹配所有元素 |
anyMatch | 是否至少匹配一个元素 |
noneMatch | 是否没有匹配所有元素 |
findFirst | 返回第一个元素 |
findAny | 返回当前流中的任意元素 |
count | 返回当前流中元素总个数 |
max | 返回流中最大值 |
min | 返回流中最小值 |
List<Integer> list = Arrays.asList(1, 2, 3, 4); // false // 当list都为1时才会返回true System.out.println(list.stream().allMatch(num -> num.equals(1))); // true System.out.println(list.stream().anyMatch(num -> num.equals(1))); // 4 System.out.println(list.stream().max((x, y) -> x.compareTo(y)).get());
归约
函数名 | 解释 |
reduce | 归约,将流中元素反复结合起来得到一个值 |
List<Integer> list = Arrays.asList(1, 2, 3, 4); int sum = list.stream().reduce(0, (x, y) -> x + y); // 10 // 初始值为0,执行过程为 // x = 0 y = 1 // x = 1 y = 2 // x = 3 y = 4 ... // 10 // 10 System.out.println(sum);
收集
用collect方法来进行收集,方法定义如下
Stream.java
<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);
当然我一般不自己实现这个接口,可以直接用Collectors工具类
@Data @AllArgsConstructor public class Student { private String name; private int age; }
List<Student> studentList = Arrays.asList(new Student("张三", 30), new Student("李四", 20), new Student("王五", 20));
List<String> nameList = studentList.stream().map(Student::getName).collect(Collectors.toList()); // [张三, 李四, 王五] System.out.println(nameList); Set<Integer> ageSet = studentList.stream().map(Student::getAge).collect(Collectors.toSet()); // [20, 30] System.out.println(ageSet); LinkedHashSet<Integer> linkedHashSet = studentList.stream().map(Student::getAge).collect(Collectors.toCollection(LinkedHashSet::new)); // [30, 20] System.out.println(linkedHashSet);
// 总数 long count = studentList.stream().collect(Collectors.counting()); // 3 System.out.println(count); // 平均值 double ageAvg = studentList.stream().collect(Collectors.averagingDouble(Student::getAge)); // 23.3 System.out.println(ageAvg); // 总和 int totalAge = studentList.stream().collect(Collectors.summingInt(Student::getAge)); // 70 System.out.println(totalAge); // 最大值 Optional<Student> student = studentList.stream().collect(Collectors.maxBy((x, y) -> x.getAge() - y.getAge())); // Student(name=张三, age=30) System.out.println(student.get()); // 按照年龄分组 // 还可以多级分组,按照年龄分组后,再按照其他条件分组,不再演示 Map<Integer, List<Student>> listMap = studentList.stream().collect(Collectors.groupingBy(Student::getAge)); // {20=[StreamDemo.Student(name=李四, age=20), StreamDemo.Student(name=王五, age=20)], 30=[StreamDemo.Student(name=张三, age=30)]} System.out.println(listMap);
一些业务场景
枚举值参数校验
项目中有很多单选项需要定义相关的枚举值,前端传入后需要校验这些值是否在枚举范围内
public enum MSG_TYPE { IMAGE((byte) 0, "图片"), TEXT((byte) 1, "文本"); public final byte value; public final String name; MSG_TYPE(byte value, String name) { this.value = value; this.name = name; } }
// 模拟前端传入的参数为1 boolean isExist = Arrays.stream(MSG_TYPE.values()).anyMatch(v -> v.value == 1); // true System.out.println(isExist); isExist = Arrays.stream(MSG_TYPE.values()).anyMatch(v -> v.value == 5); // false System.out.println(isExist);
从不同服务拿到数据进行组装
比如从A服务拿到了用户数据,从B服务拿到了用户订单数据,从C服务拿到了会员相关信息,如果想要多个服务拿到的数组信息糅合到一个数组里面,可以采用循环的方式判断填充,多层循环处理这样子是比较耗费资源
可以使用如下方法
取得3个服务的集合信息
找出共有唯一字段将其中两个集合信息进行map映射:如(用户账号 -> 会员信息),(用户账号 -> 订单信息 )
进行参数组装
@Data public static class MemberInfo{//会员信息 private String userId; private String memberId; private Integer memberLevel; private String memberDesc; //... public MemberInfo(final String userId, final String memberId, final Integer memberLevel, final String memberDesc) { this.userId = userId; this.memberId = memberId; this.memberLevel = memberLevel; this.memberDesc = memberDesc; } } @Data public static class OrderInfo{//订单信息 private String userId; private String orderId; private BigDecimal orderAmount; private Date payTime; //... public OrderInfo(final String userId, final String orderId, final BigDecimal orderAmount, final Date payTime) { this.userId = userId; this.orderId = orderId; this.orderAmount = orderAmount; this.payTime = payTime; } } @Data public static class UserInfo{//用户信息 private String userId; private String userName; private String phone; private String address; //... public UserInfo(final String userId, final String userName, final String phone, final String address) { this.userId = userId; this.userName = userName; this.phone = phone; this.address = address; } } @Data public static class QueryListInfo{//组合信息 private String userId; private String userName; private String phone; private String address; private String memberId; private Integer memberLevel; private String memberDesc; private List<OrderInfo> orderInfos; //... public QueryListInfo() { } }
组装逻辑
List<MemberInfo> memberInfos = Arrays.asList( new MemberInfo("QXZ112233", "1", 1, "大怨种低级会员"), new MemberInfo("QXZ112234", "2", 2, "大怨种中级会员"), new MemberInfo("QXZ112235", "3", 3, "大怨种高级会员") ); List<OrderInfo> orderInfos = Arrays.asList( new OrderInfo("QXZ112233", "1", new BigDecimal(100), new Date()), new OrderInfo("QXZ112233", "2", new BigDecimal(100), new Date()), new OrderInfo("QXZ112234", "3", new BigDecimal(100), new Date()), new OrderInfo("QXZ112234", "4", new BigDecimal(100), new Date()), new OrderInfo("QXZ112235", "5", new BigDecimal(100), new Date()) ); List<UserInfo> userInfos = Arrays.asList( new UserInfo("QXZ112233", "大怨种1号", "13111112222", "地球"), new UserInfo("QXZ112234", "大怨种2号", "13111113333", "地球"), new UserInfo("QXZ112235", "大怨种3号", "13111114444", "地球") ); //将数据转换成map Map<String, List<OrderInfo>> orderInfoMap = orderInfos.stream().collect(Collectors.groupingBy(OrderInfo::getUserId)); Map<String, MemberInfo> memberInfoMap = memberInfos.stream().collect(Collectors.toMap(MemberInfo::getUserId, Function.identity())); List<QueryListInfo> queryListInfos = new ArrayList<>(); for (UserInfo userInfo : userInfos) { String userId = userInfo.getUserId(); MemberInfo memberInfo = memberInfoMap.get(userId); List<OrderInfo> orderInfos1 = orderInfoMap.get(userId); //数据填充 QueryListInfo queryListInfo = new QueryListInfo(); queryListInfo.setAddress(userInfo.getAddress()); queryListInfo.setUserId(userInfo.getUserId()); queryListInfo.setUserName(userInfo.getUserName()); queryListInfo.setMemberId(memberInfo.getMemberId()); queryListInfo.setMemberLevel(memberInfo.getMemberLevel()); queryListInfo.setMemberDesc(memberInfo.getMemberDesc()); queryListInfo.setOrderInfos(orderInfos1); }