ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Apache Commons-Lang3实战:StringUtils用法与避坑

Apache Commons-Lang3实战:StringUtils用法与避坑 做Java后端的同学我敢说十个项目里有九个的依赖树里躺着Apache Commons-Lang3剩下那一个大概率是刚建的空工程还没加依赖。它不像Spring那样显眼也不像MyBatis那样有存在感但一旦你习惯了StringUtils.isBlank、ObjectUtils.defaultIfNull这些方法再回到裸写JDK工具类的日子就会浑身难受。这篇内容我想把自己这几年在项目里用commons-lang3的经验系统捋一遍——它到底解决了什么问题、哪些方法堪称神来之笔、哪些坑踩过之后才知道疼、版本怎么选、线上遇到过什么幺蛾子。不管你是刚接触这个库的新人还是用了几年但一直只用StringUtils两三个方法的半吊子都能从里面捞到点东西。1. 为什么每个Java项目几乎都躺着commons-lang31.1 从JDK自带工具类的短板说起JDK本身提供了String、Objects、Arrays这些类初看功能挺全但真到写业务代码的时候就会发现各种别扭。举个最典型的例子判断一个字符串是不是空的JDK只给了String.isEmpty()可现实业务里空的含义远比这复杂——null是空是空 一串空格在业务语义上通常也算空。你要是每次写if (str null || str.trim().isEmpty())代码里会充斥大量重复逻辑而且还容易漏掉某个分支。再比如取第一个非空值、给数组做安全判空、把对象转成可读字符串、安全地做反射调用这些需求JDK要么压根没提供要么提供得极其别扭。这就是Apache Commons-Lang3的价值所在它把Java日常开发中那些琐碎、重复、容易写错的工具方法全部给你封装好方法命名还统一用起来就像把一整套螺丝刀摆在你面前需要哪把直接拿。我个人的评价是commons-lang3不属于用了很爽的技术它属于不用会很累的基础设施。跟日志框架、JSON库一个性质平时注意不到一旦被移除就会到处报错。1.2 Maven与Gradle里怎么引入才不踩坑引入方式本身很简单但要考虑两个问题一是版本选择二是会不会跟已有的依赖冲突。dependency groupIdorg.apache.commons/groupId artifactIdcommons-lang3/artifactId version3.14.0/version /dependencyGradle的话implementation org.apache.commons:commons-lang3:3.14.0版本上我的建议是跟主流框架对齐——比如你用的Spring Boot某个版本它内部管理了commons-lang3的版本那你就别再手动指定版本号直接用父POM里的。手动指定版本号最怕的就是和Spring、MyBatis这些传递依赖进来的版本打架导致同一个类加载出两个不同版本运行时行为会变得莫名其妙。注意commons-lang3基本上没有运行时依赖是个纯净的库所以冲突风险其实很低。但低不代表没有我遇到过一次是项目里同时存在lang2.x和lang33.x因为包名不同编译时不报错运行时调用到哪个全看类加载顺序排查了两小时才定位到。1.3 lang和lang3到底差在哪别选错这是新人最容易懵的地方。老版本叫commons-langgroupId是commons-lang包名org.apache.commons.lang新版本叫commons-lang3groupId是org.apache.commons包名org.apache.commons.lang3。名字上就差了一个3但性质完全不同。对比项commons-lang (2.x)commons-lang3 (3.x)groupIdcommons-langorg.apache.commons包名org.apache.commons.langorg.apache.commons.lang3最低JDK要求JDK 1.2起JDK 1.5起新版要求更高维护状态基本停止更新持续迭代命名规范部分方法较混乱大量方法重命名、更清晰是否兼容不兼容lang3不兼容lang核心思路只有一个新项目一律用lang3老项目如果还在用lang找一个统一的时间点迁移别让两代共存。迁移的时候最大痛点就是import要全改因为包名变了编译器会直接给你报出来所以迁移本身不算难难的是别漏掉那些用反射、XML配置、注解字符串引用类名的地方。2. 字符串处理StringUtils才是日常使用频率之王2.1 isEmpty和isBlank的区别很多人答不对这俩方法的区别是面试高频题也是实际编码中必须搞清楚的。一句话概括isEmpty只认null和isBlank还额外认纯空白字符空格、制表符、换行等。StringUtils.isEmpty(null); // true StringUtils.isEmpty(); // true StringUtils.isEmpty( ); // false StringUtils.isBlank(null); // true StringUtils.isBlank(); // true StringUtils.isBlank( ); // true StringUtils.isBlank(\t\n); // true业务里到底选哪个我的经验是任何来自用户输入、外部接口的字符串几乎都应该用isBlank。因为用户很擅长输入一串空格来假装填了前端校验有时也拦不住。而isEmpty更适合那种内部已经做过trim的参数或者明确不需要考虑空白场景的地方。反向判断还有isNotEmpty和isNotBlank用的时候注意逻辑别写反了。我见过一个哥们代码里写if (!StringUtils.isBlank(str))还嫌不够直接跳过写if (StringUtils.isNotBlank(str))其实两者等价后者更易读。2.2 拆分、拼接、填充的高频姿势split在JDK里有个著名的坑a,b,.split(,)会得到[a,b]末尾的空字符串被丢弃了。如果你业务上需要保留空值比如解析CSV时列不能少JDK的行为会让你少拿到一列。commons-lang3的StringUtils.split也不保留末尾空串但它对null输入更友好——返回空数组而不是抛异常。// JDK: null 会直接抛 NPE a,b.split(,); // commons-lang3: null 返回空数组 StringUtils.split(null, ,); // 返回 String[0]拼接用StringUtils.join比手动拼StringBuilder清爽也支持数组和集合StringUtils.join(new String[]{a,b,c}, -); // a-b-c StringUtils.join(list, ,); // 集合也直接吃这里补一个小技巧StringUtils.join传入的集合元素如果是null会变成空字符串而不是null字样跟String.valueOf行为不同做数据导出的时候省了你手动过滤。填充用leftPad、rightPad比如生成订单序号、对齐报表字段StringUtils.leftPad(7, 5, 0); // 00007 StringUtils.rightPad(abc, 6, *);// abc***还有个冷门但好用的repeat和abbreviate前者重复字符串后者做省略号截断。日志打印或者生成占位符时非常好用。2.3 忽略大小写比较与包含判断业务里做字符串比较大小写经常是个坑。用户输入abc和系统存的ABC本质上应该是同一个东西。用equalsIgnoreCase能解决比较但contains没有忽略大小写的版本。commons-lang3提供了containsIgnoreCase和startsWithIgnoreCase部分版本能省去你自己转大写再比较的样板代码StringUtils.containsIgnoreCase(HelloWorld, world); // true StringUtils.equalsIgnoreCase(ABC, abc); // true注意equalsIgnoreCase内部对null做了处理两个都为null返回true一个为null返回false不会抛NPE。这个跟String.equalsIgnoreCase不同后者是实例方法null直接抛异常。所以传参可能为null的场景优先用StringUtils的版本。2.4 substring和split的边界保护JDK的substring稍微不注意就越界抛StringIndexOutOfBoundsException传null直接NPE。commons-lang3的版本做了一层保护StringUtils.substring(null, 0, 5); // null不抛异常 StringUtils.substring(abc, 10); // 越界返回空串这个行为要分两面看好处是不会因为脏数据把服务搞崩坏处是它掩盖了上游的数据问题。我实践下来的原则是在对外接口的第一道防线用commons-lang3做保护避免线上500但在内部逻辑里该抛异常的还是要显式校验别让一个本该报错的数据悄悄变成了空串最后排查问题更难。substringBetween、substringBefore、substringAfter这几个也很实用处理路径、URL、格式化报文时经常用得上StringUtils.substringBetween(tagA_content_tagA, tagA_, _tagA); // content StringUtils.substringBefore(userexample.com, ); // user StringUtils.substringAfter(userexample.com, ); // example.com2.5 StringUtils版本差异要注意不同版本的commons-lang3StringUtils方法会有增删。比如早期的StringUtils里一些方法到3.x的某些版本被标记为deprecatedtrim系列也有变化。我的避坑心得是升级前先看官方release notes尤其是大版本升级的场景。有次我把项目从3.5升到3.12编译一切正常结果线上某个用了StringUtils.defaultString重载的方法行为变了问题很隐蔽最后靠对比测试才找出来。所以库升级不要图省事小步走跑一遍单元测试。
返回列表