Apache Commons Lang 3.21.0 进阶 API 与废弃迁移详解
详解 Apache Commons Lang 3.21.0 进阶工具类 Pair、Triple、Range、ExceptionUtils、SystemUtils、builder 三件套与 MutableInt,实测 RandomUtils/RandomStringUtils 新旧 API 对照与 3.21.0 完整废弃迁移表。
Apache Commons Lang 3.21.0 进阶 API 与废弃迁移详解
目标:实测 Apache Commons Lang 3.21.0 的进阶工具类(元组 Pair/Triple、区间 Range、异常工具 ExceptionUtils、系统探测 SystemUtils、反射 builder 三件套、可变整数 MutableInt),并用
javap与编译告警双重证据梳理 RandomUtils/RandomStringUtils 的新旧 API 对照与 3.21.0 完整废弃迁移表。以下全部命令与输出均已在 Ubuntu 22.04(x86_64)实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- JDK:OpenJDK 17.0.20.1
- 构建工具:Apache Maven 3.6.3
- 目标库:org.apache.commons:commons-lang3:3.21.0(2026-09-29 发布,零传递依赖,最低 Java 8)
- 示例工程目录:/opt/lang-demo(安装过程见《Ubuntu 22.04 安装 Apache Commons Lang 指南》)
- 统一运行方式:
cd /opt/lang-demo
CP=target/classes:/root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar
java -Dfile.encoding=UTF-8 -cp $CP demo.<DemoClass>
说明:
-Dfile.encoding=UTF-8用于规避 POSIX locale 下中文输出变问号的问题。本文示例类所在的实测工程共含 6 个演示类(本系列多篇文档同源),读者按本文只创建本文用到的类时,编译输出的文件数不同属正常。
二、进阶 API 与废弃状态总览
本文覆盖的类、包位置与 3.21.0 废弃状态一览(状态均为 javap + 编译告警实测结论):
| 类 | 包位置 | 3.21.0 状态 | 新写法 |
|---|---|---|---|
| Pair / Triple | org.apache.commons.lang3.tuple | 正常,未废弃 | 直接使用 |
| Range | org.apache.commons.lang3 | between(T,T) 已 @Deprecated | Range.of(T,T) |
| ExceptionUtils | org.apache.commons.lang3.exception | 正常 | 直接使用 |
| SystemUtils / JavaVersion | org.apache.commons.lang3 | 正常 | 直接使用 |
| EqualsBuilder / HashCodeBuilder / ToStringBuilder | org.apache.commons.lang3.builder | 正常 | 直接使用 |
| MutableInt | org.apache.commons.lang3.mutable | getValue() 已 @Deprecated | intValue() |
| RandomStringUtils | org.apache.commons.lang3 | 类未废弃;19 个旧静态方法 + 构造器全部 @Deprecated | insecure()/secure()/secureStrong() 工厂 + 实例方法 |
| RandomUtils | org.apache.commons.lang3 | 类未废弃;11 个旧静态方法 + 构造器全部 @Deprecated | 同上 + 实例方法 |
三、前置条件:工程与依赖确认
沿用《Ubuntu 22.04 安装 Apache Commons Lang 指南》的 Maven 工程。确认依赖与编译告警基线(-Xlint:deprecation 已在 pom 的 maven-compiler-plugin 中开启):
cd /opt/lang-demo && mvn -B compile
输出节选(实测,compile 段与结尾;仅保留本文涉及的 WARNING):
[INFO] Compiling 6 source files with javac [debug release 17] to target/classes
[WARNING] /opt/lang-demo/src/main/java/demo/RandomDemo.java:[9,74] nextInt(int,int) in org.apache.commons.lang3.RandomUtils has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/RandomDemo.java:[10,79] nextDouble(double,double) in org.apache.commons.lang3.RandomUtils has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/RandomDemo.java:[11,72] nextBoolean() in org.apache.commons.lang3.RandomUtils has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/RandomDemo.java:[14,90] randomAlphabetic(int) in org.apache.commons.lang3.RandomStringUtils has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/RandomDemo.java:[15,87] randomNumeric(int) in org.apache.commons.lang3.RandomStringUtils has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/TupleRangeDemo.java:[29,33] <T>between(T,T) in org.apache.commons.lang3.Range has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/TupleRangeDemo.java:[38,34] <T>between(T,T) in org.apache.commons.lang3.Range has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/TupleRangeDemo.java:[41,34] <T>between(T,T) in org.apache.commons.lang3.Range has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/ExceptionSystemDemo.java:[37,75] <T>defaultIfNull(T,T) in org.apache.commons.lang3.ObjectUtils has been deprecated
[WARNING] /opt/lang-demo/src/main/java/demo/ExceptionSystemDemo.java:[52,46] getValue() in org.apache.commons.lang3.mutable.MutableInt has been deprecated
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] Total time: 1.318 s
这批 WARNING 正是第十一章废弃迁移总表的原始依据。
四、Pair 与 Triple:lang3.tuple 元组
Java 没有内建元组,Lang 的 Pair/Triple 补上了「一次返回两个/三个值」的表达。两个类都在 org.apache.commons.lang3.tuple 子包(不在 lang3 顶层,import 写错是高频错误)。
4.1 包位置核实(javap)
JAR=/root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar
jar tf $JAR | grep -iE "triple|/Pair"
javap -cp $JAR org.apache.commons.lang3.tuple.Triple | head -12
输出:
org/apache/commons/lang3/tuple/ImmutableTriple.class
org/apache/commons/lang3/tuple/MutableTriple.class
org/apache/commons/lang3/tuple/Pair.class
org/apache/commons/lang3/tuple/Triple.class
Compiled from "Triple.java"
public abstract class org.apache.commons.lang3.tuple.Triple<L, M, R> implements java.lang.Comparable<org.apache.commons.lang3.tuple.Triple<L, M, R>>, java.io.Serializable {
public static final org.apache.commons.lang3.tuple.Triple<?, ?, ?>[] EMPTY_ARRAY;
public static <L, M, R> org.apache.commons.lang3.tuple.Triple<L, M, R>[] emptyArray();
public static <L, M, R> org.apache.commons.lang3.tuple.Triple<L, M, R> of(L, M, R);
public static <L, M, R> org.apache.commons.lang3.tuple.Triple<L, M, R> ofNonNull(L, M, R);
public org.apache.commons.lang3.tuple.Triple();
public int compareTo(org.apache.commons.lang3.tuple.Triple<L, M, R>);
public boolean equals(java.lang.Object);
public abstract L getLeft();
public abstract M getMiddle();
public abstract R getRight();
事实:
Pair.of/Triple.of工厂在 3.21.0 未废弃(编译无告警);Triple抽象类实现Comparable与Serializable。
4.2 功能实测:Map.Entry 与 Comparable 能力
源码 /opt/lang-demo/src/main/java/demo/TupleRangeDemo.java(完整,Range 部分见第五章):
package demo;
import java.util.Map;
import org.apache.commons.lang3.Range;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.commons.lang3.tuple.Pair;
public class TupleRangeDemo {
public static void main(String[] args) {
System.out.println("== Pair ==");
Pair<String, Integer> pair = Pair.of("age", 30);
System.out.println("Pair.of(\"age\", 30) = " + pair);
System.out.println("getClass = " + pair.getClass().getName());
System.out.println("getLeft = " + pair.getLeft() + ", getKey = " + pair.getKey());
System.out.println("getRight = " + pair.getRight() + ", getValue = " + pair.getValue());
System.out.println("equals(Pair.of(\"age\",30)) = " + pair.equals(Pair.of("age", 30)));
System.out.println("instanceof Map.Entry = " + (pair instanceof Map.Entry));
System.out.println("instanceof Comparable = " + (pair instanceof Comparable));
System.out.println("compareTo(Pair.of(\"age\",31)) = " + pair.compareTo(Pair.of("age", 31)));
System.out.println("== Triple ==");
Triple<String, Integer, Boolean> triple = Triple.of("flag", 1, true);
System.out.println("Triple.of(\"flag\", 1, true) = " + triple);
System.out.println("getClass = " + triple.getClass().getName());
System.out.println("getLeft = " + triple.getLeft() + ", getMiddle = " + triple.getMiddle() + ", getRight = " + triple.getRight());
System.out.println("== Range ==");
Range<Integer> r = Range.between(1, 10);
System.out.println("Range.between(1, 10) = " + r);
System.out.println("contains(5) = " + r.contains(5) + ", contains(10) = " + r.contains(10) + ", contains(0) = " + r.contains(0));
System.out.println("isBefore(11) = " + r.isBefore(11) + " // 整个范围在 11 之前");
System.out.println("isBefore(10) = " + r.isBefore(10));
System.out.println("isAfter(0) = " + r.isAfter(0) + " // 整个范围在 0 之后");
System.out.println("isAfter(1) = " + r.isAfter(1));
System.out.println("getMinimum = " + r.getMinimum() + ", getMaximum = " + r.getMaximum());
System.out.println("fit(15) = " + r.fit(15) + ", fit(-3) = " + r.fit(-3) + ", fit(7) = " + r.fit(7));
Range<Integer> r2 = Range.between(5, 15);
System.out.println("isOverlappedBy([5,15]) = " + r.isOverlappedBy(r2));
System.out.println("intersectionWith([5,15]) = " + r.intersectionWith(r2));
Range<Integer> r3 = Range.between(20, 30);
try {
r.intersectionWith(r3);
} catch (IllegalArgumentException e) {
System.out.println("intersectionWith([20,30]) 抛出 IllegalArgumentException: " + e.getMessage());
}
}
}
运行真实回显(Pair/Triple 段):
== Pair ==
Pair.of("age", 30) = (age,30)
getClass = org.apache.commons.lang3.tuple.ImmutablePair
getLeft = age, getKey = age
getRight = 30, getValue = 30
equals(Pair.of("age",30)) = true
instanceof Map.Entry = true
instanceof Comparable = true
compareTo(Pair.of("age",31)) = -1
== Triple ==
Triple.of("flag", 1, true) = (flag,1,true)
getClass = org.apache.commons.lang3.tuple.ImmutableTriple
getLeft = flag, getMiddle = 1, getRight = true
要点:
Pair.of返回不可变的ImmutablePair;同时实现了Map.Entry(instanceof 实测 true)与Comparable。- 具名访问器
getLeft/getRight与Map.Entry的getKey/getValue两套并存,可直接当 Map.Entry 用(如放进 Map 迭代)。 compareTo先按 key 再按 value 比较:("age",30)对("age",31)返回 -1(实测)。Triple.of返回ImmutableTriple,getMiddle访问中间元素;toString 形如(flag,1,true)。- 与 JDK
AbstractMap.SimpleEntry的差异:SimpleEntry 可变(支持 setValue)、无 compareTo 语义、也没有 getLeft/getRight 具名访问器。
五、Range:区间与废弃迁移
Range 表达「闭区间」并内置边界判断。3.21.0 中 between(T,T) 已 @Deprecated,官方推荐工厂 Range.of(T,T)(编译告警实录见第三章)。
5.1 完整成员核实:没有 with 方法
网上教程常写 range.with(新边界) 之类的「链式调整」API——3.21.0 的 Range 没有 with 方法。用 javap 全成员列表证实:
javap -cp /root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar org.apache.commons.lang3.Range
输出(完整,可逐行核对):
Compiled from "Range.java"
public class org.apache.commons.lang3.Range<T> implements java.io.Serializable {
public static <T extends java.lang.Comparable<? super T>> org.apache.commons.lang3.Range<T> between(T, T);
public static <T> org.apache.commons.lang3.Range<T> between(T, T, java.util.Comparator<T>);
public static <T extends java.lang.Comparable<? super T>> org.apache.commons.lang3.Range<T> is(T);
public static <T> org.apache.commons.lang3.Range<T> is(T, java.util.Comparator<T>);
public static <T extends java.lang.Comparable<? super T>> org.apache.commons.lang3.Range<T> of(T, T);
public static <T> org.apache.commons.lang3.Range<T> of(T, T, java.util.Comparator<T>);
org.apache.commons.lang3.Range(T, T, java.util.Comparator<T>);
public boolean contains(T);
public boolean containsRange(org.apache.commons.lang3.Range<T>);
public int elementCompareTo(T);
public boolean equals(java.lang.Object);
public T fit(T);
public java.util.Comparator<T> getComparator();
public T getMaximum();
public T getMinimum();
public int hashCode();
public org.apache.commons.lang3.Range<T> intersectionWith(org.apache.commons.lang3.Range<T>);
public boolean isAfter(T);
public boolean isAfterRange(org.apache.commons.lang3.Range<T>);
public boolean isBefore(T);
public boolean isBeforeRange(org.apache.commons.lang3.Range<T>);
public boolean isEndedBy(T);
public boolean isNaturalOrdering();
public boolean isOverlappedBy(org.apache.commons.lang3.Range<T>);
public boolean isStartedBy(T);
public java.lang.String toString();
public java.lang.String toString(java.lang.String);
}
要点:
- 工厂方法共三个:
between(已废弃)、is(单点闭区间)、of(推荐)。 - Range 不可变(字段 final、无任何 setter/with),「调整边界」的正确表达是创建新的 Range;把任意值「夹进」区间用
fit(T)。 - 相近能力方法:
fit(夹取)、intersectionWith(交集,不相交时抛异常)、isOverlappedBy(是否相交)。
5.2 功能实测
源码见 4.2 的 TupleRangeDemo.java(Range 段),运行真实回显:
== Range ==
Range.between(1, 10) = [1..10]
contains(5) = true, contains(10) = true, contains(0) = false
isBefore(11) = true // 整个范围在 11 之前
isBefore(10) = false
isAfter(0) = true // 整个范围在 0 之后
isAfter(1) = false
getMinimum = 1, getMaximum = 10
fit(15) = 10, fit(-3) = 1, fit(7) = 7
isOverlappedBy([5,15]) = true
intersectionWith([5,15]) = [5..10]
intersectionWith([20,30]) 抛出 IllegalArgumentException: Cannot calculate intersection with non-overlapping range [20..30]
要点:
- 区间是闭区间:
contains(10) = true(上边界包含)。 isBefore/isAfter是「整个区间在目标之前/之后」的语义:isBefore(10) = false、isBefore(11) = true(实测)。fit把越界值夹到边界:15 → 10、-3 → 1、区间内原样返回。intersectionWith对不相交区间抛IllegalArgumentException(实测报错文案如上),先isOverlappedBy再求交集更稳。- 迁移写法:
Range.between(1, 10)改为Range.of(1, 10),两者行为一致(输出同为[1..10])。
六、ExceptionUtils:异常链工具
源码 /opt/lang-demo/src/main/java/demo/ExceptionSystemDemo.java(完整源码同时覆盖 ObjectUtils/MutableInt/DurationFormatUtils,本节讲异常与系统探测部分):
package demo;
import org.apache.commons.lang3.JavaVersion;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.lang3.time.DurationFormatUtils;
public class ExceptionSystemDemo {
public static void main(String[] args) {
System.out.println("== ExceptionUtils ==");
try {
try {
throw new IllegalArgumentException("root-cause-msg");
} catch (IllegalArgumentException e) {
throw new RuntimeException("outer-msg", e);
}
} catch (Exception e) {
String st = ExceptionUtils.getStackTrace(e);
System.out.println("getStackTrace(e) 总行数 = " + st.split("\n").length);
System.out.println("getThrowableCount(e) = " + ExceptionUtils.getThrowableCount(e));
System.out.println("getRootCause(e) = " + ExceptionUtils.getRootCause(e));
}
System.out.println("== SystemUtils / JavaVersion ==");
System.out.println("IS_OS_LINUX = " + SystemUtils.IS_OS_LINUX);
System.out.println("IS_OS_MAC = " + SystemUtils.IS_OS_MAC);
System.out.println("IS_OS_WINDOWS = " + SystemUtils.IS_OS_WINDOWS);
System.out.println("JAVA_VERSION = " + SystemUtils.JAVA_VERSION);
System.out.println("IS_JAVA_1_8 = " + SystemUtils.IS_JAVA_1_8);
System.out.println("JavaVersion.JAVA_17 = " + JavaVersion.JAVA_17);
System.out.println("JavaVersion.JAVA_RECENT = " + JavaVersion.JAVA_RECENT);
System.out.println("isJavaVersionAtLeast(JAVA_17) = " + SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_17));
System.out.println("== ObjectUtils ==");
System.out.println("defaultIfNull(null, \"dft\") = " + ObjectUtils.defaultIfNull(null, "dft"));
System.out.println("firstNonNull(null, \"x\") = " + ObjectUtils.firstNonNull(null, "x"));
System.out.println("firstNonNull(null, null) = " + ObjectUtils.firstNonNull(null, null));
System.out.println("allNull(null, null) = " + ObjectUtils.allNull(null, null));
System.out.println("allNull(null, \"a\") = " + ObjectUtils.allNull(null, "a"));
System.out.println("anyNull(\"a\", null) = " + ObjectUtils.anyNull("a", null));
System.out.println("anyNull(\"a\", \"b\") = " + ObjectUtils.anyNull("a", "b"));
System.out.println("== MutableInt ==");
MutableInt counter = new MutableInt(0);
counter.increment();
counter.increment();
System.out.println("increment() x2 后 = " + counter.intValue());
System.out.println("addAndGet(5) = " + counter.addAndGet(5));
System.out.println("getAndAdd(2) = " + counter.getAndAdd(2) + " (返回加之前的值)");
System.out.println("最终值 = " + counter.getValue());
System.out.println("== DurationFormatUtils ==");
System.out.println("formatDuration(3661000, \"H:mm:ss\") = " + DurationFormatUtils.formatDuration(3661000L, "H:mm:ss"));
System.out.println("formatDuration(3661000, \"HH:mm:ss\") = " + DurationFormatUtils.formatDuration(3661000L, "HH:mm:ss"));
System.out.println("formatDurationWords(90061000, true, false) = " + DurationFormatUtils.formatDurationWords(90061000L, true, false));
}
}
运行真实回显(ExceptionUtils 段):
== ExceptionUtils ==
getStackTrace(e) 总行数 = 4
getThrowableCount(e) = 2
getRootCause(e) = java.lang.IllegalArgumentException: root-cause-msg
要点:
getStackTrace把整条异常链格式化为字符串(日志落盘的标准姿势),实测该双层异常为 4 行(异常两行 +Caused by两行)。getThrowableCount数异常链长度:外层 RuntimeException + 内层 IllegalArgumentException = 2。getRootCause一行挖到最内层根因,免去手写 while 循环遍历 cause。
七、SystemUtils 与 JavaVersion:环境探测
7.1 基础探测实测
源码见第六章 ExceptionSystemDemo.java(SystemUtils 段),运行真实回显:
== SystemUtils / JavaVersion ==
IS_OS_LINUX = true
IS_OS_MAC = false
IS_OS_WINDOWS = false
JAVA_VERSION = 17.0.20.1
IS_JAVA_1_8 = false
JavaVersion.JAVA_17 = 17
JavaVersion.JAVA_RECENT = 17.0
isJavaVersionAtLeast(JAVA_17) = true
要点:
IS_OS_LINUX等静态布尔直接判定操作系统,来自os.name系统属性。SystemUtils.JAVA_VERSION反映运行 JVM 的实际版本(本环境 17.0.20.1;同样代码在 JDK 17.0.1 的环境输出 17.0.1,已实测)。- 判「Java 17+」三种写法:
SystemUtils.IS_JAVA_17、SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_17)、JavaVersion.JAVA_RECENT对比,实测均可用。
7.2 IS_JAVA 字段全集与取值核实
SystemUtils 的 IS_JAVA 布尔字段从 1_1 一直提供到 28(javap 全集实测):
javap -cp /root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar org.apache.commons.lang3.SystemUtils | grep "IS_JAVA"
输出:
public static final boolean IS_JAVA_1_1;
public static final boolean IS_JAVA_1_2;
public static final boolean IS_JAVA_1_3;
public static final boolean IS_JAVA_1_4;
public static final boolean IS_JAVA_1_5;
public static final boolean IS_JAVA_1_6;
public static final boolean IS_JAVA_1_7;
public static final boolean IS_JAVA_1_8;
public static final boolean IS_JAVA_1_9;
public static final boolean IS_JAVA_9;
public static final boolean IS_JAVA_10;
public static final boolean IS_JAVA_11;
public static final boolean IS_JAVA_12;
public static final boolean IS_JAVA_13;
public static final boolean IS_JAVA_14;
public static final boolean IS_JAVA_15;
public static final boolean IS_JAVA_16;
public static final boolean IS_JAVA_17;
public static final boolean IS_JAVA_18;
public static final boolean IS_JAVA_19;
public static final boolean IS_JAVA_20;
public static final boolean IS_JAVA_21;
public static final boolean IS_JAVA_22;
public static final boolean IS_JAVA_23;
public static final boolean IS_JAVA_24;
public static final boolean IS_JAVA_25;
public static final boolean IS_JAVA_26;
public static final boolean IS_JAVA_27;
public static final boolean IS_JAVA_28;
JDK 17 环境下的取值核实(验证类 /tmp/IsJavaCheck.java,完整源码):
import org.apache.commons.lang3.SystemUtils;
public class IsJavaCheck {
public static void main(String[] args) {
System.out.println("IS_JAVA_1_8 = " + SystemUtils.IS_JAVA_1_8);
System.out.println("IS_JAVA_9 = " + SystemUtils.IS_JAVA_9);
System.out.println("IS_JAVA_11 = " + SystemUtils.IS_JAVA_11);
System.out.println("IS_JAVA_17 = " + SystemUtils.IS_JAVA_17);
System.out.println("IS_JAVA_21 = " + SystemUtils.IS_JAVA_21);
}
}
编译运行:
JAR=/root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar
javac -cp $JAR -d /tmp /tmp/IsJavaCheck.java && java -cp /tmp:$JAR IsJavaCheck
输出:
IS_JAVA_1_8 = false
IS_JAVA_9 = false
IS_JAVA_11 = false
IS_JAVA_17 = true
IS_JAVA_21 = false
事实:没有名为
JAVA_VERSION_17的字段——按版本探测用IS_JAVA_17(布尔)或JavaVersion.JAVA_17(枚举常量),不要凭记忆拼字段名。本环境 JDK 17 下IS_JAVA_17实测为 true,1_8/9/11/21 均为 false。
八、builder 三件套:equals、hashCode、toString
org.apache.commons.lang3.builder 的三个 builder 消灭手写样板代码。源码 /opt/lang-demo/src/main/java/demo/BuilderDemo.java(完整):
package demo;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class BuilderDemo {
public static class User {
private final String name;
private final int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (o == null || o.getClass() != getClass()) {
return false;
}
User u = (User) o;
return new EqualsBuilder().append(name, u.name).append(age, u.age).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(name).append(age).toHashCode();
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("name", name).append("age", age).toString();
}
}
public static void main(String[] args) {
User a = new User("alice", 30);
User b = new User("alice", 30);
User c = new User("bob", 40);
System.out.println("== ToStringBuilder(手动 append, SHORT_PREFIX_STYLE) ==");
System.out.println("a = " + a);
System.out.println("== EqualsBuilder / HashCodeBuilder(手动 append) ==");
System.out.println("a.equals(b) = " + a.equals(b));
System.out.println("a.equals(c) = " + a.equals(c));
System.out.println("a.hashCode() = " + a.hashCode());
System.out.println("b.hashCode() = " + b.hashCode());
System.out.println("== reflection 方式 ==");
System.out.println("EqualsBuilder.reflectionEquals(a, b) = " + EqualsBuilder.reflectionEquals(a, b));
System.out.println("EqualsBuilder.reflectionEquals(a, c) = " + EqualsBuilder.reflectionEquals(a, c));
System.out.println("HashCodeBuilder.reflectionHashCode(a) = " + HashCodeBuilder.reflectionHashCode(a));
System.out.println("HashCodeBuilder.reflectionHashCode(b) = " + HashCodeBuilder.reflectionHashCode(b));
System.out.println("ToStringBuilder.reflectionToString(a, JSON_STYLE) = " + ToStringBuilder.reflectionToString(a, ToStringStyle.JSON_STYLE));
System.out.println("ReflectionToStringBuilder.toString(c) = " + ReflectionToStringBuilder.toString(c));
}
}
运行真实回显:
== ToStringBuilder(手动 append, SHORT_PREFIX_STYLE) ==
a = BuilderDemo.User[name=alice,age=30]
== EqualsBuilder / HashCodeBuilder(手动 append) ==
a.equals(b) = true
a.equals(c) = false
a.hashCode() = -857531513
b.hashCode() = -857531513
== reflection 方式 ==
EqualsBuilder.reflectionEquals(a, b) = true
EqualsBuilder.reflectionEquals(a, c) = false
HashCodeBuilder.reflectionHashCode(a) = 92927423
HashCodeBuilder.reflectionHashCode(b) = 92927423
ToStringBuilder.reflectionToString(a, JSON_STYLE) = {"age":30,"name":"alice"}
ReflectionToStringBuilder.toString(c) = demo.BuilderDemo$User@58372a00[age=40,name=bob]
要点:
- 手动方式在 equals/hashCode/toString 里逐字段
append,字段增减时编译期可见、可控;HashCodeBuilder(17, 37)的两个参数是自定义的初始值与乘子。 - 手动 HashCodeBuilder(17,37) 与 reflectionHashCode 的哈希值不同(实测 -857531513 对 92927423,反射版用默认 multiplier/initializer),但同值对象在同一方式下恒等——equals 与 hashCode 必须成对同方式实现。
- reflection 方式零样板,直接按反射字段比较/生成;
JSON_STYLE输出按字段名排序的 JSON 形态(实测{"age":30,"name":"alice"})。 ReflectionToStringBuilder默认 style 的输出带内部类全名与 identityHashCode(实测demo.BuilderDemo$User@58372a00[age=40,name=bob]),生产日志建议显式指定 style。
九、MutableInt:可变整数与废弃迁移
MutableInt 避免「计数器场景反复装箱」——注意它在 org.apache.commons.lang3.mutable 子包(import 写成 lang3 顶层或 lang3.math 都会编译失败)。
9.1 功能实测
源码见第六章 ExceptionSystemDemo.java(MutableInt 段),运行真实回显:
== MutableInt ==
increment() x2 后 = 2
addAndGet(5) = 7
getAndAdd(2) = 7 (返回加之前的值)
最终值 = 9
要点:
increment()无返回值;addAndGet(int)返回加完的新值(7);getAndAdd(int)返回加之前的值(7),语义对齐AtomicInteger。- 最终值 9 = 2 + 5 + 2(实测口径自洽)。
9.2 getValue 废弃与 intValue 替代(实测)
getValue() 在 3.21.0 已 @Deprecated(第三章 WARNING 实录)。替代方法是 intValue(),实测编译无告警(验证类 DepCheck.java 放在 /opt/lang-jars,完整源码与运行说明见《Apache Commons Lang 3.21.0 常用 API 与工具类详解》7.1 节):
cd /opt/lang-jars
javac -Xlint:deprecation -encoding UTF-8 -cp /root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar -d . DepCheck.java
java -Dfile.encoding=UTF-8 -cp .:/root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar DepCheck
输出(实测,mi.intValue() 与 mi.incrementAndGet() 均无 deprecated 告警):
getIfNull(null, "dft") = dft
getIfNull(null, supplier) = dft-supp
mi.intValue() = 3
mi.incrementAndGet() = 4
迁移写法:counter.getValue() 逐字替换为 counter.intValue()(或需要 String 时 toString()),行为无差异。
十、RandomUtils 与 RandomStringUtils:新旧 API 全对照
这是 3.21.0 变化最大的一块。两个类本身没有被废弃,但全部旧静态方法 + 公共构造器逐一标注了 @Deprecated;新用法是 insecure()/secure()/secureStrong() 三个工厂 + 实例方法。
10.1 废弃状态核实(javap 逐成员)
列出 RandomStringUtils 中每个被 @Deprecated 的成员:
JAR=/root/.m2/repository/org/apache/commons/commons-lang3/3.21.0/commons-lang3-3.21.0.jar
javap -v -cp $JAR org.apache.commons.lang3.RandomStringUtils | awk '/^ [a-z]/{m=$0} /Deprecated: true/{if (m) print m; m=""}' | sort | uniq -c | sort -rn
输出(19 个静态方法 + 1 个构造器,共 20 条):
1 public static java.lang.String randomPrint(int, int);
1 public static java.lang.String randomPrint(int);
1 public static java.lang.String randomNumeric(int, int);
1 public static java.lang.String randomNumeric(int);
1 public static java.lang.String randomGraph(int, int);
1 public static java.lang.String randomGraph(int);
1 public static java.lang.String randomAscii(int, int);
1 public static java.lang.String randomAscii(int);
1 public static java.lang.String randomAlphanumeric(int, int);
1 public static java.lang.String randomAlphanumeric(int);
1 public static java.lang.String randomAlphabetic(int, int);
1 public static java.lang.String randomAlphabetic(int);
1 public static java.lang.String random(int, java.lang.String);
1 public static java.lang.String random(int, int, int, boolean, boolean, char...);
1 public static java.lang.String random(int, int, int, boolean, boolean);
1 public static java.lang.String random(int, char...);
1 public static java.lang.String random(int, boolean, boolean);
1 public static java.lang.String random(int);
1 public org.apache.commons.lang3.RandomStringUtils();
再看新 API 在类上的完整形态:
javap -cp $JAR org.apache.commons.lang3.RandomStringUtils
输出节选(完整输出略长,仅截取三类关键行):
Compiled from "RandomStringUtils.java"
public class org.apache.commons.lang3.RandomStringUtils {
public static org.apache.commons.lang3.RandomStringUtils insecure();
public static java.lang.String random(int);
public static java.lang.String randomAlphabetic(int);
public static org.apache.commons.lang3.RandomStringUtils secure();
public static org.apache.commons.lang3.RandomStringUtils secureStrong();
public org.apache.commons.lang3.RandomStringUtils();
public java.lang.String next(int);
public java.lang.String nextAlphabetic(int);
public java.lang.String nextAlphabetic(int, int);
public java.lang.String nextAlphanumeric(int);
public java.lang.String nextAscii(int);
public java.lang.String nextGraph(int);
public java.lang.String nextNumeric(int);
public java.lang.String nextPrint(int);
public java.lang.String toString();
static {};
}
RandomUtils 完全同构(11 个旧静态方法 + 构造器废弃):
javap -cp $JAR org.apache.commons.lang3.RandomUtils
输出节选(截取关键行):
Compiled from "RandomUtils.java"
public class org.apache.commons.lang3.RandomUtils {
public static org.apache.commons.lang3.RandomUtils insecure();
public static boolean nextBoolean();
public static byte[] nextBytes(int);
public static double nextDouble();
public static double nextDouble(double, double);
public static float nextFloat();
public static float nextFloat(float, float);
public static int nextInt();
public static int nextInt(int, int);
public static long nextLong();
public static long nextLong(long, long);
public static org.apache.commons.lang3.RandomUtils secure();
static java.security.SecureRandom secureRandom();
public static org.apache.commons.lang3.RandomUtils secureStrong();
public org.apache.commons.lang3.RandomUtils();
java.util.Random random();
public boolean randomBoolean();
public byte[] randomBytes(int);
public double randomDouble();
public double randomDouble(double, double);
public float randomFloat();
public float randomFloat(float, float);
public int randomInt();
public int randomInt(int, int);
public long randomLong();
public long randomLong(long, long);
public java.lang.String toString();
static {};
}
结论:RandomStringUtils 的实例方法统一叫
next前缀(next、nextAlphabetic、nextAlphanumeric、nextAscii、nextGraph、nextNumeric、nextPrint 及对应区间重载),RandomUtils 的实例方法统一叫random前缀(randomBoolean、randomInt、randomLong、randomDouble、randomFloat、randomBytes 等),这些实例方法均未废弃。
10.2 新旧调用实测对照
源码 /opt/lang-demo/src/main/java/demo/RandomDemo.java(完整,故意不加 @SuppressWarnings 以保留编译告警):
package demo;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
public class RandomDemo {
public static void main(String[] args) {
System.out.println("== RandomUtils 旧静态 API(3.21.0 中已 @Deprecated) ==");
System.out.println("RandomUtils.nextInt(0, 100) = " + RandomUtils.nextInt(0, 100));
System.out.println("RandomUtils.nextDouble(0.0, 1.0) = " + RandomUtils.nextDouble(0.0, 1.0));
System.out.println("RandomUtils.nextBoolean() = " + RandomUtils.nextBoolean());
System.out.println("== RandomStringUtils 旧静态 API(3.21.0 中已 @Deprecated) ==");
System.out.println("RandomStringUtils.randomAlphabetic(8) = " + RandomStringUtils.randomAlphabetic(8));
System.out.println("RandomStringUtils.randomNumeric(6) = " + RandomStringUtils.randomNumeric(6));
System.out.println("== 新实例化 API(insecure/secure 工厂 + 实例方法,未废弃) ==");
System.out.println("RandomStringUtils.insecure().nextAlphabetic(8) = " + RandomStringUtils.insecure().nextAlphabetic(8));
System.out.println("RandomStringUtils.secure().nextNumeric(6) = " + RandomStringUtils.secure().nextNumeric(6));
System.out.println("RandomUtils.insecure().randomInt(0, 100) = " + RandomUtils.insecure().randomInt(0, 100));
System.out.println("RandomUtils.secure().randomLong(0, 1000) = " + RandomUtils.secure().randomLong(0, 1000));
}
}
编译告警(旧静态 API 全部命中,新 API 无告警):
[WARNING] RandomDemo.java:[9,74] nextInt(int,int) in org.apache.commons.lang3.RandomUtils has been deprecated
[WARNING] RandomDemo.java:[10,79] nextDouble(double,double) in org.apache.commons.lang3.RandomUtils has been deprecated
[WARNING] RandomDemo.java:[11,72] nextBoolean() in org.apache.commons.lang3.RandomUtils has been deprecated
[WARNING] RandomDemo.java:[14,90] randomAlphabetic(int) in org.apache.commons.lang3.RandomStringUtils has been deprecated
[WARNING] RandomDemo.java:[15,87] randomNumeric(int) in org.apache.commons.lang3.RandomStringUtils has been deprecated
运行真实回显(随机值每次运行都不同):
== RandomUtils 旧静态 API(3.21.0 中已 @Deprecated) ==
RandomUtils.nextInt(0, 100) = 19
RandomUtils.nextDouble(0.0, 1.0) = 0.5537731872062437
RandomUtils.nextBoolean() = false
== RandomStringUtils 旧静态 API(3.21.0 中已 @Deprecated) ==
RandomStringUtils.randomAlphabetic(8) = XADctLLO
RandomStringUtils.randomNumeric(6) = 789065
== 新实例化 API(insecure/secure 工厂 + 实例方法,未废弃) ==
RandomStringUtils.insecure().nextAlphabetic(8) = zbCIPigo
RandomStringUtils.secure().nextNumeric(6) = 280973
RandomUtils.insecure().randomInt(0, 100) = 49
RandomUtils.secure().randomLong(0, 1000) = 881
新旧调用对照与迁移写法:
| 场景 | 旧静态 API(已废弃) | 新实例 API(推荐) |
|---|---|---|
| 随机字母串 | RandomStringUtils.randomAlphabetic(8) | RandomStringUtils.insecure().nextAlphabetic(8) |
| 随机数字串 | RandomStringUtils.randomNumeric(6) | RandomStringUtils.secure().nextNumeric(6) |
| 区间随机整数 | RandomUtils.nextInt(0, 100) | RandomUtils.insecure().randomInt(0, 100) |
| 随机布尔 | RandomUtils.nextBoolean() | RandomUtils.insecure().randomBoolean() |
| 区间随机长整数 | RandomUtils.nextLong(0, 1000) | RandomUtils.secure().randomLong(0, 1000) |
| 随机字节数组 | RandomUtils.nextBytes(n) | RandomUtils.secure().randomBytes(n) |
说明:旧静态方法 3.21.0 仍可正常运行(输出真实有效),仅产生编译告警;存量代码建议按上表迁移,新代码直接用实例 API。
💡 技术拓展:insecure()/secure()/secureStrong() 的安全性差异与选型——
工厂 随机源 性能 适用场景 insecure()java.util.Random(线程局部缓存)高 测试数据、展示样例、非冲突短 ID 等非安全场景 secure()SecureRandom(平台默认算法)中 验证码、令牌、盐值等常规安全需求 secureStrong()SecureRandom(高强度算法,首次初始化较慢)低 密钥材料等高安全需求(兼顾合规要求时) 选型原则:能明确「这串随机数不用于安全目的」就用 insecure()(性能最好且语义清晰);涉及令牌、验证码、防猜测试探一律 secure() 起步;安全审计要求高熵随机(如密钥生成辅助)再考虑 secureStrong()。三者返回同一个 RandomStringUtils/RandomUtils 实例类型,切换只改工厂方法。
十一、3.21.0 废弃清单总表与迁移写法
汇总本文与《Apache Commons Lang 3.21.0 常用 API 与工具类详解》实测确认的全部废弃点(证据:第三章 11 条编译告警 + 10.1 javap 逐成员核实):
| 废弃 API | 状态 | 实测证据 | 替代写法 |
|---|---|---|---|
RandomStringUtils 全部 19 个旧静态方法 + 构造器 | @Deprecated | javap Deprecated: true 逐成员 + 编译告警 | insecure()/secure()/secureStrong() 工厂 + next 前缀实例方法 |
RandomUtils 全部 11 个旧静态方法 + 构造器 | @Deprecated | javap + 编译告警 | 工厂 + random 前缀实例方法 |
Range.between(T,T) | @Deprecated | 编译告警(3 处) | Range.of(T,T) |
StringUtils.defaultString(String,String) | @Deprecated | 编译告警 | 单参 defaultString(str) / 三元表达式 / Objects.toString(str, default) |
ObjectUtils.defaultIfNull(T,T) | @Deprecated | 编译告警 | ObjectUtils.getIfNull(obj, default)(编译实测无告警) |
MutableInt.getValue() | @Deprecated | 编译告警 | intValue()(编译实测无告警) |
三条迁移注意事项:
- 类未被废弃 ≠ 方法未废弃:RandomStringUtils/RandomUtils 两个类在 3.21.0 可正常 import 与使用,但旧静态方法调用全部产生告警(10.2 实测),新代码不要照旧写。
- 替代 API 已实测无告警:
Range.of、getIfNull、intValue()、next/random前缀实例方法编译干净,可放心作为迁移目标。 Pair.of/Triple.of未废弃:元组工厂与 builder 三件套在 3.21.0 状态稳定,无需迁移。
十二、常见问题(FAQ)
Q1:RandomStringUtils 在 3.21.0 里被整体废弃了吗?
没有。类本身未 @Deprecated(javap 类声明无 Deprecated 标记),被废弃的是 19 个旧静态方法 + 构造器(10.1 逐成员清单实测)。推荐用法是 RandomStringUtils.insecure().nextAlphabetic(8) 这类「工厂 + 实例方法」组合,编译零告警。
Q2:secure() 与 secureStrong() 有什么区别?
两者都基于 SecureRandom,javadoc 语义上 secure() 使用平台默认强度算法,secureStrong() 面向更高强度需求(首次初始化更慢)。常规安全场景(验证码、令牌)用 secure() 即可;非安全场景用 insecure()(性能最优)。三者在本环境 javap 输出中均存在且未废弃(10.1 实测)。
Q3:Range 为什么没有 with 方法?
3.21.0 的 Range 是不可变对象,工厂(of/is/between)创建后没有任何修改边界的方法——javap 全成员列表可逐行核对(5.1 实测,不存在 with)。需要「调整边界」就创建新 Range;需要「把值夹进区间」用 fit(T);需要「合并/相交判断」用 intersectionWith(不相交会抛 IllegalArgumentException)与 isOverlappedBy。
Q4:Pair 能直接当 Map.Entry 用吗?
能。实测 instanceof Map.Entry = true(4.2 回显),getKey/getValue 可用,equals 兼容任意 Map.Entry 实现;同时实现 Comparable(先 key 后 value),可参与排序。
Q5:为什么找不到 SystemUtils.JAVA_VERSION_17 这个字段?
字段名不存在。SystemUtils 的版本探测字段是 IS_JAVA_1_1 到 IS_JAVA_28 的布尔族(含 IS_JAVA_17,javap 全集与取值实测见 7.2)以及 isJavaVersionAtLeast(JavaVersion.JAVA_17) 方法;枚举常量是 JavaVersion.JAVA_17。写代码前用 javap 核实字段名可避免此类想当然。
十三、参考链接
- Pair / Triple 官方 Javadoc:https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/tuple/package-summary.html
- Range 官方 Javadoc:https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/Range.html
- ExceptionUtils 官方 Javadoc:https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/exception/ExceptionUtils.html
- SystemUtils 官方 Javadoc:https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/SystemUtils.html
- builder 包官方 Javadoc:https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/builder/package-summary.html
- RandomStringUtils 官方 Javadoc(含 insecure/secure/secureStrong 说明):https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/RandomStringUtils.html
- 常用工具类篇:《Apache Commons Lang 3.21.0 常用 API 与工具类详解》