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 LangApache Commons Lang2026/10/15

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 / Tripleorg.apache.commons.lang3.tuple正常,未废弃直接使用
Rangeorg.apache.commons.lang3between(T,T) 已 @DeprecatedRange.of(T,T)
ExceptionUtilsorg.apache.commons.lang3.exception正常直接使用
SystemUtils / JavaVersionorg.apache.commons.lang3正常直接使用
EqualsBuilder / HashCodeBuilder / ToStringBuilderorg.apache.commons.lang3.builder正常直接使用
MutableIntorg.apache.commons.lang3.mutablegetValue() 已 @DeprecatedintValue()
RandomStringUtilsorg.apache.commons.lang3类未废弃;19 个旧静态方法 + 构造器全部 @Deprecatedinsecure()/secure()/secureStrong() 工厂 + 实例方法
RandomUtilsorg.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 个旧静态方法 + 构造器@Deprecatedjavap Deprecated: true 逐成员 + 编译告警insecure()/secure()/secureStrong() 工厂 + next 前缀实例方法
RandomUtils 全部 11 个旧静态方法 + 构造器@Deprecatedjavap + 编译告警工厂 + 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()(编译实测无告警)

三条迁移注意事项:

  1. 类未被废弃 ≠ 方法未废弃:RandomStringUtils/RandomUtils 两个类在 3.21.0 可正常 import 与使用,但旧静态方法调用全部产生告警(10.2 实测),新代码不要照旧写。
  2. 替代 API 已实测无告警:Range.of、getIfNull、intValue()、next/random 前缀实例方法编译干净,可放心作为迁移目标。
  3. 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 核实字段名可避免此类想当然。

十三、参考链接

  1. Pair / Triple 官方 Javadoc:https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/tuple/package-summary.html
  2. Range 官方 Javadoc:https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/Range.html
  3. ExceptionUtils 官方 Javadoc:https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/exception/ExceptionUtils.html
  4. SystemUtils 官方 Javadoc:https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/SystemUtils.html
  5. builder 包官方 Javadoc:https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/builder/package-summary.html
  6. RandomStringUtils 官方 Javadoc(含 insecure/secure/secureStrong 说明):https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/RandomStringUtils.html
  7. 常用工具类篇:《Apache Commons Lang 3.21.0 常用 API 与工具类详解》