大家好,我是吉森。
前面给大家安利了谷歌的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
注意事项
- 从3.0版本起,commons-lang更名为commons-lang3,请注意引入的时候名称不要错误
- 本文仅列举了部分常用api,整个工具包的内容非常广泛。如有需要,可到官网进一步深入学习。
整理不易,如果本文对你帮助,欢迎转发点赞支持~