Java8 Stream sorted排序实战:从Comparator基础到多级排序进阶
1. 从零开始理解Stream sorted排序第一次接触Java8的Stream sorted方法时我盯着那段链式调用的代码看了足足十分钟。就像刚拿到新手机的老人明明按键就在眼前却不知道从哪下手。后来在实际项目中踩过几次坑才明白sorted()本质上就是个智能排序器而Comparator就是告诉它排序规则的说明书。先看最简单的自然排序。假设我们有个字符串列表ListString names Arrays.asList(张三, 李四, 王五);用names.stream().sorted().forEach(System.out::println);就能按字典序输出。这就像把一堆杂乱的书本按书名首字母排列但现实中的需求往往更复杂。有次我处理用户数据时发现直接用sorted()抛出了异常——原来列表里的自定义对象没实现Comparable接口就像试图给一堆没有条形码的商品排序系统根本不知道谁该排在前面。这时候就需要祭出Comparator了。最基础的写法是用lambda表达式list.stream().sorted((o1, o2) - o1.getAge() - o2.getAge())。这个减号操作符就像天平当左边大于右边返回正数就是升序反过来就是降序。不过实际开发中我更喜欢用Comparator.comparing(User::getAge)这种写法代码更语义化就像直接告诉系统嘿按年龄排序。2. Comparator的七十二变Comparator的灵活之处在于它能像乐高积木一样组合。记得有次做电商项目商品要按销量降序排列销量相同的再按价格升序排。这用传统写法得嵌套多层if-else而用Stream sorted只需要products.stream() .sorted(Comparator.comparing(Product::getSales).reversed() .thenComparing(Product::getPrice)) .collect(Collectors.toList());这里的reversed()就像把排序规则倒过来看而thenComparing相当于说前面的规则分不出胜负时再用这个新规则。这种链式调用比俄罗斯套娃式的if-else清爽多了。更复杂的场景比如处理多语言排序时可以用Comparator.comparing(String::length, Comparator.reverseOrder())实现先按字符串长度降序再按字母顺序升序。这就像图书馆先按书厚度分大类再按书名细分。实测下来这种写法比传统Collections.sort性能更好特别是在并行流处理时。3. 多级排序的实战技巧真实项目中的排序需求往往像洋葱一样有多层。去年做HR系统时遇到个典型场景员工要先按部门字母顺序排同部门再按职级倒序职级相同的再按入职时间正序。用thenComparing组合起来就像搭积木employees.stream() .sorted(Comparator.comparing(Employee::getDepartment) .thenComparing(Employee::getLevel, Comparator.reverseOrder()) .thenComparing(Employee::getHireDate)) .forEach(System.out::println);这里有个容易踩的坑thenComparing连接的每个Comparator必须独立完整。有次我写成.thenComparing(Employee::getLevel.reversed())直接编译报错因为reversed()不是字段自带的属性。正确做法是像上面那样用Comparator.reverseOrder()或者用方法引用加比较器.thenComparing(Employee::getLevel, (a,b)- b.compareTo(a))。对于可能为null的字段可以用Comparator.nullsFirst()或Comparator.nullsLast()。比如处理用户列表时有些用户可能没填生日users.stream() .sorted(Comparator.comparing(User::getBirthday, Comparator.nullsLast(Comparator.naturalOrder())))这相当于把空值当作无穷大或无穷小处理避免出现NullPointerException。4. 性能优化与特殊场景当处理大数据量时排序可能成为性能瓶颈。有次我处理10万条日志数据发现sorted()操作耗时占整个流程的70%。后来通过测试发现几个优化点对于已经实现Comparable的对象直接使用sorted()比sorted(Comparator.comparing(...))更快因为少了一层包装在多级排序中把过滤条件高的字段放在前面能减少后续比较次数对于固定排序规则可以预编译Comparatorprivate static final ComparatorEmployee EMP_COMPARATOR Comparator.comparing(Employee::getDepartment) .thenComparingInt(Employee::getLevel);这样每次排序时就不用重复创建比较器实例了。在百万级数据测试中这种写法能提升约15%的性能。另一个特殊场景是对中文排序。直接用sorted()会按Unicode编码排可能不符合预期。可以用Collator实现本地化排序Collator zhCollator Collator.getInstance(Locale.CHINA); users.stream() .sorted(Comparator.comparing(User::getName, zhCollator))处理日期字符串时也要小心比如2021-9-1和2021-10-1按字符串排序会出错应该先转成LocalDate再比较Comparator.comparing(s - LocalDate.parse(s, DateTimeFormatter.ofPattern(yyyy-M-d)))5. 那些年我踩过的排序坑在实际项目中遇到过几个值得分享的案例。有次做订单导出功能要求按订单状态分组后每组内部按金额降序排。第一版代码写成orders.stream() .sorted(Comparator.comparing(Order::getStatus) .thenComparing(Order::getAmount).reversed())结果发现金额全是升序——原来reversed()作用的是整个Comparator链。正确写法应该是.thenComparing(Order::getAmount, Comparator.reverseOrder())还有个内存溢出的坑。有次对数据库查询结果直接做sorted().limit(100)结果当数据量很大时sorted()会先把所有数据加载到内存。后来改用数据库分页查询内存排序结合的方式解决。对于自定义复杂排序规则比如VIP用户优先然后按活跃度但黑名单用户永远在最后可以这样写ComparatorUser vipFirst (u1, u2) - { if(u1.isBlacklist() ! u2.isBlacklist()) { return u1.isBlacklist() ? 1 : -1; } if(u1.isVip() ! u2.isVip()) { return u1.isVip() ? -1 : 1; } return Integer.compare(u1.getActivity(), u2.getActivity()); };这种写法虽然不如链式调用优雅但在复杂业务规则下更灵活可控。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2525077.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!