Apache commons工具包——让你的java鸟枪换炮(一)

Apache commons工具包——让你的java鸟枪换炮(一)

精选文章moguli202025-02-13 11:33:2315A+A-

大家好,我是吉森。

前面给大家安利了谷歌的guava工具包和国产的hutool工具包,收到了热烈的讨论,在这里先谢谢你们的热情。

我们都知道java的官方类库——jdk的内容不够丰富,不能完全满足各式各样的开发需求。为了提升效率,我们通常会引入一些第三方的工具包,这也符合java小官方、大社区的惯例。但是对于引入什么样的工具包,却有不少讨论和争议。以前面提到的hutool为例,有的同学认为类库的代码质量一般,表示不用;有的同学认为没有经过充分的测试,表示不用;有的同学认为开源的协议过于小众有风险,表示不用。

那么今天开始我们介绍的这款大名鼎鼎Apache Commons工具包,可以说完全没有上述风险。想必大部分做java开发的同学都听说过它的名字,它是apache基金会负责维护的,代码质量高,经过了充分的测试,它的开源协议Apache License 2.0可以说是最宽松的开源协议。总之,大家可以放心使用。

Apache Commons是一个非常庞大的工具包,有多达43个模块,我计划分几次为大家整理出最常用的模块的脑图和api,可以方便大家用最少的时间熟悉它。

今天我们先从一个核心模块——lang模块开始。顾名思义,它是语言模块,是对java.lang包的一个丰富和补充。目前最新版本为3.12,包名为commons-lang3。先来看一下思维导图:

再来看一下常用的API:

基础工具——数组处理

ArrayUtils类提供方便地处理数组的工具,包括对数组进行添加元素、删除元素和翻转等操作。

使用场景:对数组进行简单高效地处理。

 // 向数组尾端插入元素,得到的是一个新建的数组,原数组为null则新建数组
int[] add0 = ArrayUtils.add(new int[]{1}, 3); // [1, 3]
int[] add1 = ArrayUtils.add((int[]) null, 3); // [3]

// 像数组指定位置插入若干个元素,得到的是一个复制的数组,原数组为null则直接返回null
int[] insert0 = ArrayUtils.insert(0, new int[]{0}, 1, 2, 3); // [0, 1, 2, 3]
int[] insert1 = ArrayUtils.insert(0, (int[]) null, 1, 2, 3); // null

// 移除下标为2的元素
int[] remove = ArrayUtils.remove(new int[]{1, 2, 3, 4}, 2); // [1, 2, 4]

// 根据值移除元素
int[] removeElement = ArrayUtils.removeElement(new int[]{1, 2, 3, 4}, 2); // [1, 3, 4]

// 原地翻转数组,注意此方法改变了输入参数
int[] array = {1, 2, 3, 4, 5}; 
ArrayUtils.reverse(array); // [5, 4, 3, 2, 1]

基础工具——随机工具

RandomUtils提供了生成随机整数、浮点数等的方法,RandomStringUtils提供了由数字、字母、其他符号组成的指定长度的随机字符串的方法。

使用场景:生成随机数、生成随机密码、生成序列号等。

/* RandomUtils */

// 生成[0, 5)之间的随机数,含头不含尾
int randomInt = RandomUtils.nextInt(0, 5);

// 生成[0.0, 5.0)之间的随机数,含头不含尾
double randomDouble = RandomUtils.nextDouble(0.0, 5.0);

/* RandomStringUtils */

// 生成由6位随机数字组成的字符串
String randomNumeric = RandomStringUtils.randomNumeric(6);

// 生成由6-9位数字或字母组成的字符串
String randomAlphanumeric = RandomStringUtils.randomAlphanumeric(6, 10);

基础工具——字符串处理

StringUtils提供了方便的处理字符串的方式,RegExUtils提供了基于正则表达式处理字符串的方式,特别是基于正则表达式删除字符串一部分的功能。

使用场景:字符串拼接、字符串判空、字符串删除内容以及其他字符串处理

/* StringUtils */

String s = "  ";

// 判断字符串是否为空(null或空串)
boolean empty = StringUtils.isEmpty(s); // false

// 判断字符串是否为空白字符串(null、空串或只包含空格)
boolean blank = StringUtils.isBlank(s); // true

// 拼接由分隔符分隔的字符串
String join = StringUtils.joinWith(",", "a", null, "b"); // "a,,b"

/*  RegExUtils */

// 按正则表达式移除字符串的内容,removePattern启动了DOTALL模式,则.号也可以匹配\n
String s1 = RegExUtils.removePattern("a12b3\nc", "3.c"); // "a12b"
String s2 = RegExUtils.removeAll("a12b3\nc", "3.c"); // "a12b3\nc"

基础工具——对象工具

ObjectUtils提供了一些对于一般对象进行比较、判空、取最大最小值、中值的方法。SerializationUtils可以用于对象深拷贝。

使用场景:针对可排序对象取最大、最小值、中位数时,如取一个班级学生最高分(需提前定义比较策略);对象深拷贝。

 /* ObjectUtils */

// 比较两个对象,这两个对象必须是Comparable的,如果s1大,返回1;s2大,返回-1;相等返回0。
int compare = ObjectUtils.compare(s1, s2);

// 取最大值
String max = ObjectUtils.max(s1, s2, s3, s4);

// 取中值
String median = ObjectUtils.median(s1, s2, s3, s4);

 /* SerializationUtils */

// 通过序列化和反序列化实现对象的深拷贝
String clone = SerializationUtils.clone(obj);

并发——初始化

ConcurrentInitializer是一个父级接口,用于线程安全地初始化对象。它有多个不同的实现类,包括AtomicInitializer、AtomicSafeInitializer、BackgroundInitializer、ConstantInitializer、LazyInitializer、MultiBackgroundInitializer等。

ConstantInitializer顾名思义,是用常量初始化对象。

LazyInitializer也是见名知义,用于对象懒加载。

AtomicInitializer与LazyInitializer很像,也用于对象懒加载,在内部是基于AtomReference实现的,不需要同步锁,效率更高。

AtomicSafeInitializer一样也用于对象懒加载,它额外加了一层检查,保证initialize方法只被调用一次。

BackgroundInitializer和MultiBackgroundInitializer用额外线程初始化对象,它们可以将一些耗时的初始化操作并行处理,提高运行效率,后者是前者的一个实现类,可以同时处理多个后台任务。

使用场景:在多线程的情况下安全地初始化对象

// 字符串将在首次使用时初始化为abc,此处的LazyInitializer可以替换为AtomicInitializer或AtomicSafeInitializer
ConcurrentInitializer initializer = new LazyInitializer<>() {
            @Override
            protected String initialize() {
                return "abc";
            }
        };

// 在某个Runnable对象中实际使用字符串,调用ConcurrentInitializer对象的get方法
public void run() {
    try {
        String s = initializer.get();
    } catch (ConcurrentException cex) {
        cex.printStackTrace();
    }
}

并发——工具类

ConcurrentUtils用于简化ConcurrentInitializer使用过程中的样板代码,如:

try {
    String s = initializer.get();
} catch (ConcurrentException cex) {
    cex.printStackTrace();
}

可以简化为

String s = ConcurrentUtils.initializeUnchecked(initializer);

使用场景:配合ConcurrentInitializer使用

并发——同步

commons-lang3中扩展了一个信号量的实现类,即TimedSemaphore,用于在给定时间范围内允许完成一些操作。

它和JUC包中的Semaphore类有点类似,区别在于,TimedSemaphore没有提供release方法,所有的限制将在时间结束后自动释放。

这里参考一下官方的示例,可以理解更加准确。

// 一个用于统计的线程类
public class StatisticsThread extends Thread {

    // 限制数据库负载的信号量
    private final TimedSemaphore semaphore;

    // 初始化实例,同时设置信息量
    public StatisticsThread(TimedSemaphore timedSemaphore) {
        semaphore = timedSemaphore;
    }

    // 收集统计数据
    public void run() {
        try {
            while (true) {
                // 调用acquire方法限制数据库访问次数
                semaphore.acquire();   

                // 执行数据库访问
                performQuery();        
            }
        } catch(InterruptedException) {
            // 异常处理
        }
    }
    ...
}

// 信号量声明为1秒10次,即1秒可以访问10次数据库
TimedSemaphore sem = new TimedSemaphore(1, TimeUnit.SECOND, 10);
StatisticsThread thread = new StatisticsThread(sem);
thread.start();

元组

元组就是可以存放不同类型对象的一个小容器,很多编程语言在语言特性层面支持元组。很遗憾,java又一次不在这个很多语言里面。commons-lang3里提供的Pair和Triple类可以满足一定场景下元组的使用。

使用场景:一个方法想要返回两个或三个值

/* Pair */
// 创建一个二元组
Pair pair = Pair.of(123, "abc");

// 获取二元组的第一个值,使用getLeft或getKey
final Integer left = pair.getLeft();

// 获取二元组的第二个值,使用getRight或getValue
final String right = pair.getRight();

/* Triple */
final Triple triple = Triple.of(123, 456.0, "abc");

// 获取三元组第一个值
final Integer first = triple.getLeft();

// 获取三元组第二个值
final Double second = triple.getMiddle();

// 获取三元组第三个值
final String third = triple.getRight();

函数式编程

jdk8在java语言层面引入了更多函数式编程的组件,包括lambda表达式和一些函数式接口,如Consumer、Supplier、Predicate、Function等。这些接口的一个问题是没有考虑抛出异常的情况,这使得我们在异常处理的场景下,编码变得很麻烦。

commons-lang的function包在jdk8提供的函数式接口的基础上提供了一套允许失败场景的版本,如FailableConsumer、FailableSupplier、FailablePredicate、FailableFunction等等。这些类的补充,可以让我们函数式编程的代码更加优雅简洁。

使用场景:函数式编程中处理异常的情况

// 消费者接口在过程中可能出现异常
Consumer consumer = m -> {
    try {
        m.invoke(o, args);
    } catch (Throwable t) {
        throw Failable.rethrow(t);
    }
};

// 使用FailableConsumer的lambda表达式实现,配合Failable.accept方法,可以更优雅地重抛异常
Failable.accept((m) -> m.invoke(o, args), method);

可变基本类型

在java中存大八大基本数据类型,这些数据类型在泛型等场景下,需要使用Integer、Long等包装类。这些包装类有一个特点:它们都是不可变的。如果基础类型值需要频繁变动,使用Integer等包装类将会大量创建对象,降低效率。

commons-lang提供了一套可变的包装类,如MutableInt、MutableLong等,可以解决jdk中包装类不可变的问题。

使用场景:1.需要基本类型作为方法参数并且让方法改变该参数的值;2.集合中频繁改变基本类型的值

// Mutable版本的交换函数
public static void swap(MutableInt a, MutableInt b) {
    int c = a.getValue();
    a.setValue(b);
    b.setValue(c);
}

MutableInt a = new MutableInt(1);
MutableInt b = new MutableInt(2);

swap(a, b); // a = 2, b = 1

反射

FieldUtils、ConstructorUtils、MethodUtils等类补充了java反射的一些方法,一定程度上解决了jdk自带反射api不好用的问题。

出于篇幅考虑,这里只演示一部分api。

使用场景:简化java反射

/* FieldUtils */
// 获取所有成员变量,解决Class类的getFields和getDeclaredFields获取字段不全的问题。
Field[] allFields = FieldUtils.getAllFields(clazz);

// 读取o对象某个属性name的值,允许将其改变为accessible
Object bytes = FieldUtils.readDeclaredField(o, "name", true);

// 获取被@Excel注解标注的字段
List fieldsListWithAnnotation = FieldUtils.getFieldsListWithAnnotation(Student.class, Excel.class);

/* ConstructorUtils */
// 获取可以访问的构造器
Constructor accessibleConstructor = ConstructorUtils.getAccessibleConstructor(clazz, String.class);

// 调用构造器
String s5 = ConstructorUtils.invokeConstructor(String.class, "a");

/* MethodUtils *.
// 调用静态方法
Object result = MethodUtils.invokeStaticMethod(String.class, "valueOf", 123);

数学

Fraction类可以用于模拟分数运算,NumberUtils类可以用于求最大、最小值,判断字符串是否要以转换成数字等。

使用场景:数学运算

/* Fraction */
// 构造分数,4/5
Fraction fraction = Fraction.getFraction(4, 5);

// 分数求和
Fraction sum = fraction.add(Fraction.getFraction(3, 7));

// 将分数转为浮点数
double d = fraction.doubleValue();

/* NumberUtils */
// 获取多个数的最大值,相比之下Math.max方法只能比较两个数字
int max = NumberUtils.max(1, 2, 3, 4, 5);

// 判断一个字符串是否为可转换的数字
boolean parsable = NumberUtils.isParsable("abc"); // false

引入apache的commons-lang3

在项目中可以通过maven引入commons-lang3库,方式如下:


    org.apache.commons
    commons-lang3
    3.12.0

注意事项

  1. 从3.0版本起,commons-lang更名为commons-lang3,请注意引入的时候名称不要错误
  2. 本文仅列举了部分常用api,整个工具包的内容非常广泛。如有需要,可到官网进一步深入学习。

整理不易,如果本文对你帮助,欢迎转发点赞支持~


点击这里复制本文地址 以上内容由莫古技术网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

莫古技术网 © All Rights Reserved.  滇ICP备2024046894号-2