1. 引言
在日常 java 开发中,我们经常需要处理一些重复性的基础工作,比如日期格式化、字符串校验、集合操作、文件读写等。虽然 jdk 提供了丰富的 api,但直接使用往往代码冗长、容易出错。本文将分享一套我自己封装并持续维护的 java 工具类合集,涵盖日期、字符串、集合、文件、加密、并发等高频场景,帮助大家减少重复代码、提升开发效率。
所有工具类均基于 jdk 8+ 编写,无任何第三方依赖,可直接复制到项目中使用。
工具类总览
下表汇总了本文将要介绍的 6 个工具类,方便你快速了解每个工具类的核心能力与适用场景:
| 工具类名称 | 核心功能 | 主要方法 | 适用场景 |
|---|---|---|---|
| dateutils | 日期时间格式化、解析、计算与区间判断 | now()、format()、parse()、daysbetween()、isbetween() | 日志时间戳、业务时间计算、时间区间校验 |
| stringutils | 字符串判空、拼接、脱敏与格式校验 | isempty()、isblank()、join()、maskmobile()、isnumeric() | 参数校验、手机号脱敏、文本拼接 |
| collectionutils | 集合判空、去重、分批与转换 | isempty()、isnotempty()、distinctbykey()、partition()、tomap() | 批量数据处理、列表去重、分批入库 |
| fileutils | 文件读写、复制与递归删除 | readfile()、writefile()、copyfile()、deleterecursively() | 配置文件读取、临时文件清理、文件备份 |
| encryptutils | md5、sha-256 哈希加密 | md5()、sha256() | 密码存储、数据完整性校验 |
| threadutils | 线程休眠、线程池创建与超时执行 | sleep()、newfixedpool()、executewithtimeout() | 异步任务、定时休眠、带超时的远程调用 |
2. 日期时间工具类 dateutils
日期处理是开发中最常见的需求之一。这里封装了格式化、解析、计算、区间判断等常用方法。
import java.time.localdate;
import java.time.localdatetime;
import java.time.format.datetimeformatter;
import java.time.temporal.chronounit;
/**
* 日期时间工具类(基于 java.time,线程安全)
*/
public final class dateutils {
private static final string default_pattern = "yyyy-mm-dd hh:mm:ss";
private dateutils() {
}
/** 格式化当前时间 */
public static string now() {
return format(localdatetime.now(), default_pattern);
}
/** 按指定格式格式化时间 */
public static string format(localdatetime datetime, string pattern) {
return datetime.format(datetimeformatter.ofpattern(pattern));
}
/** 解析字符串为 localdatetime */
public static localdatetime parse(string datetimestr, string pattern) {
return localdatetime.parse(datetimestr, datetimeformatter.ofpattern(pattern));
}
/** 计算两个日期相差的天数 */
public static long daysbetween(localdate start, localdate end) {
return chronounit.days.between(start, end);
}
/** 判断某个时间是否在区间内(含边界) */
public static boolean isbetween(localdatetime target, localdatetime start, localdatetime end) {
return !target.isbefore(start) && !target.isafter(end);
}
}
3. 字符串工具类 stringutils
字符串判空、去空格、拼接、脱敏是业务代码中的高频操作,封装后可以让代码更简洁。
import java.util.arrays;
import java.util.list;
import java.util.stream.collectors;
/**
* 字符串工具类
*/
public final class stringutils {
private stringutils() {
}
/** 判断字符串是否为空(null 或空串) */
public static boolean isempty(string str) {
return str == null || str.isempty();
}
/** 判断字符串是否为空(null、空串或全空白) */
public static boolean isblank(string str) {
return str == null || str.trim().isempty();
}
/** 使用分隔符拼接字符串列表 */
public static string join(list<string> list, string delimiter) {
return list.stream().collect(collectors.joining(delimiter));
}
/** 手机号脱敏:保留前 3 后 4 位 */
public static string maskmobile(string mobile) {
if (isblank(mobile) || mobile.length() != 11) {
return mobile;
}
return mobile.replaceall("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
/** 判断字符串是否为纯数字 */
public static boolean isnumeric(string str) {
if (isblank(str)) {
return false;
}
return str.chars().allmatch(character::isdigit);
}
}
4. 集合工具类 collectionutils
集合判空、分组、去重、转换是日常开发中使用频率极高的操作。
import java.util.*;
import java.util.function.function;
import java.util.stream.collectors;
/**
* 集合工具类
*/
public final class collectionutils {
private collectionutils() {
}
/** 判断集合是否为空 */
public static boolean isempty(collection<?> collection) {
return collection == null || collection.isempty();
}
/** 判断集合是否非空 */
public static boolean isnotempty(collection<?> collection) {
return !isempty(collection);
}
/** 按指定字段去重 */
public static <t> list<t> distinctbykey(list<t> list, function<? super t, ?> keyextractor) {
set<object> seen = new hashset<>();
return list.stream()
.filter(e -> seen.add(keyextractor.apply(e)))
.collect(collectors.tolist());
}
/** 将列表按指定大小分批 */
public static <t> list<list<t>> partition(list<t> list, int size) {
if (isempty(list) || size <= 0) {
return collections.emptylist();
}
list<list<t>> result = new arraylist<>();
for (int i = 0; i < list.size(); i += size) {
result.add(new arraylist<>(list.sublist(i, math.min(i + size, list.size()))));
}
return result;
}
/** 将列表转换为 map(key 冲突时保留第一个) */
public static <t, k> map<k, t> tomap(list<t> list, function<? super t, ? extends k> keyextractor) {
return list.stream().collect(collectors.tomap(keyextractor, function.identity(), (a, b) -> a));
}
}
5. 文件工具类 fileutils
文件读写、复制、删除等操作封装后,可以避免大量样板代码。
import java.io.*;
import java.nio.charset.standardcharsets;
import java.nio.file.*;
/**
* 文件工具类
*/
public final class fileutils {
private fileutils() {
}
/** 读取文件全部内容为字符串 */
public static string readfile(string path) throws ioexception {
return new string(files.readallbytes(paths.get(path)), standardcharsets.utf_8);
}
/** 写入字符串到文件(自动创建父目录) */
public static void writefile(string path, string content) throws ioexception {
path filepath = paths.get(path);
if (filepath.getparent() != null) {
files.createdirectories(filepath.getparent());
}
files.write(filepath, content.getbytes(standardcharsets.utf_8));
}
/** 复制文件 */
public static void copyfile(string source, string target) throws ioexception {
files.copy(paths.get(source), paths.get(target), standardcopyoption.replace_existing);
}
/** 递归删除目录 */
public static void deleterecursively(file file) throws ioexception {
if (file.isdirectory()) {
file[] children = file.listfiles();
if (children != null) {
for (file child : children) {
deleterecursively(child);
}
}
}
files.deleteifexists(file.topath());
}
}
6. 加密工具类 encryptutils
md5、sha-256 等哈希算法常用于密码存储和完整性校验。
import java.nio.charset.standardcharsets;
import java.security.messagedigest;
import java.security.nosuchalgorithmexception;
/**
* 加密工具类
*/
public final class encryptutils {
private encryptutils() {
}
/** 计算 md5 哈希(转十六进制字符串) */
public static string md5(string input) {
return hash(input, "md5");
}
/** 计算 sha-256 哈希(转十六进制字符串) */
public static string sha256(string input) {
return hash(input, "sha-256");
}
private static string hash(string input, string algorithm) {
try {
messagedigest digest = messagedigest.getinstance(algorithm);
byte[] bytes = digest.digest(input.getbytes(standardcharsets.utf_8));
stringbuilder sb = new stringbuilder();
for (byte b : bytes) {
sb.append(string.format("%02x", b));
}
return sb.tostring();
} catch (nosuchalgorithmexception e) {
throw new runtimeexception("不支持的算法: " + algorithm, e);
}
}
}
7. 并发工具类 threadutils
线程休眠、线程池创建等操作封装后,可以让并发代码更简洁、更安全。
import java.util.concurrent.*;
/**
* 并发工具类
*/
public final class threadutils {
private threadutils() {
}
/** 线程休眠(不抛出受检异常) */
public static void sleep(long millis) {
try {
thread.sleep(millis);
} catch (interruptedexception e) {
thread.currentthread().interrupt();
}
}
/** 创建固定大小线程池(带命名工厂) */
public static executorservice newfixedpool(int size, string poolname) {
threadfactory factory = new threadfactory() {
private int count = 0;
@override
public thread newthread(runnable r) {
return new thread(r, poolname + "-" + (++count));
}
};
return executors.newfixedthreadpool(size, factory);
}
/** 带超时地执行任务,返回结果或默认值 */
public static <t> t executewithtimeout(callable<t> task, long timeout, timeunit unit, t defaultvalue) {
executorservice executor = executors.newsinglethreadexecutor();
try {
future<t> future = executor.submit(task);
return future.get(timeout, unit);
} catch (exception e) {
return defaultvalue;
} finally {
executor.shutdownnow();
}
}
}
性能与边界条件
使用上述工具类时,有几个性能与边界条件需要特别留意:
- executewithtimeout 的线程池开销:该方法每次调用都会通过
executors.newsinglethreadexecutor()新建一个线程池,并在finally中调用shutdownnow()销毁。在高频调用场景下,频繁创建和销毁线程池会带来不小的资源开销,甚至可能成为性能瓶颈。建议在频繁调用时复用同一个线程池,例如将线程池提升为类的静态成员,或由调用方统一创建并传入。 - sleep 的中断处理:
sleep方法捕获interruptedexception后调用thread.currentthread().interrupt()恢复中断标志,而不是吞掉异常。这样调用方仍可通过thread.interrupted()感知中断状态,避免中断信号被静默丢失,是推荐的中断处理策略。 - newfixedpool 的线程数设置:线程池大小应根据任务类型合理设置。cpu 密集型任务建议设置为
cpu 核数 + 1,i/o 密集型任务可适当调大(如cpu 核数 * 2),避免线程数过少导致吞吐不足,或过多导致上下文切换开销增大。
jmh 基准测试:新建线程池 vs 复用静态线程池
为了量化「每次新建线程池」与「复用静态线程池」两种实现方式的性能差异,下面使用 jmh(java microbenchmark harness)编写基准测试,分别在 1000、10000、100000 次调用下对比两者的吞吐量和平均耗时。
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.blackhole;
import org.openjdk.jmh.runner.runner;
import org.openjdk.jmh.runner.options.options;
import org.openjdk.jmh.runner.options.optionsbuilder;
import java.util.concurrent.*;
import java.util.concurrent.atomic.atomicinteger;
/**
* jmh 基准测试:executewithtimeout 两种实现方式对比
* 运行方式:mvn clean package && java -jar target/benchmarks.jar
*/
@benchmarkmode({mode.throughput, mode.averagetime})
@outputtimeunit(timeunit.milliseconds)
@warmup(iterations = 3, time = 1)
@measurement(iterations = 5, time = 1)
@fork(1)
@threads(1)
public class threadutilsbenchmark {
/** 方式一:每次调用新建线程池(对应原实现) */
public static <t> t executewithtimeoutnew(callable<t> task, long timeout, timeunit unit, t defaultvalue) {
executorservice executor = executors.newsinglethreadexecutor();
try {
future<t> future = executor.submit(task);
return future.get(timeout, unit);
} catch (exception e) {
return defaultvalue;
} finally {
executor.shutdownnow();
}
}
/** 方式二:复用静态线程池 */
private static final executorservice shared_executor = executors.newsinglethreadexecutor();
public static <t> t executewithtimeoutshared(callable<t> task, long timeout, timeunit unit, t defaultvalue) {
try {
future<t> future = shared_executor.submit(task);
return future.get(timeout, unit);
} catch (exception e) {
return defaultvalue;
}
}
private static callable<integer> task() {
return () -> 42;
}
@benchmark
public void newpool_1000(blackhole bh) {
for (int i = 0; i < 1000; i++) {
bh.consume(executewithtimeoutnew(task(), 1, timeunit.seconds, -1));
}
}
@benchmark
public void sharedpool_1000(blackhole bh) {
for (int i = 0; i < 1000; i++) {
bh.consume(executewithtimeoutshared(task(), 1, timeunit.seconds, -1));
}
}
@benchmark
public void newpool_10000(blackhole bh) {
for (int i = 0; i < 10000; i++) {
bh.consume(executewithtimeoutnew(task(), 1, timeunit.seconds, -1));
}
}
@benchmark
public void sharedpool_10000(blackhole bh) {
for (int i = 0; i < 10000; i++) {
bh.consume(executewithtimeoutshared(task(), 1, timeunit.seconds, -1));
}
}
@benchmark
public void newpool_100000(blackhole bh) {
for (int i = 0; i < 100000; i++) {
bh.consume(executewithtimeoutnew(task(), 1, timeunit.seconds, -1));
}
}
@benchmark
public void sharedpool_100000(blackhole bh) {
for (int i = 0; i < 100000; i++) {
bh.consume(executewithtimeoutshared(task(), 1, timeunit.seconds, -1));
}
}
public static void main(string[] args) throws exception {
options opt = new optionsbuilder()
.include(threadutilsbenchmark.class.getsimplename())
.build();
new runner(opt).run();
}
}
测试结果(示例数据,实际以本机环境为准):
| 调用次数 | 实现方式 | 吞吐量(ops/ms) | 平均耗时(ms/op) |
|---|---|---|---|
| 1000 | 每次新建线程池 | 约 0.8 | 约 1.25 |
| 1000 | 复用静态线程池 | 约 12.5 | 约 0.08 |
| 10000 | 每次新建线程池 | 约 0.7 | 约 1.43 |
| 10000 | 复用静态线程池 | 约 13.0 | 约 0.077 |
| 100000 | 每次新建线程池 | 约 0.6 | 约 1.67 |
| 100000 | 复用静态线程池 | 约 12.8 | 约 0.078 |
测试结论与推荐方案:
- 复用静态线程池的吞吐量约为每次新建线程池的 15~20 倍,平均耗时从毫秒级降到亚毫秒级。调用次数越多,差距越明显,100000 次调用时新建线程池的耗时已接近复用方案的 20 倍。
- 推荐方案:在频繁调用
executewithtimeout的场景下,务必复用线程池。可将线程池提升为threadutils的静态成员,或由调用方统一创建并传入。同时注意,复用线程池后需在应用关闭时显式调用shutdown()释放资源,避免线程泄漏。 - 补充说明:若任务本身耗时较长(如远程调用),线程池创建开销占比会相对下降,但复用线程池仍能显著降低调度成本,且能避免频繁创建线程带来的 gc 压力。
异常处理与注意事项
工具类封装了常用能力,但异常处理策略各不相同。使用前先了解哪些方法会抛出受检异常、哪些异常被包装或吞掉,能避免在业务代码里踩坑。
1. 受检异常:谁抛出、如何应对
fileutils:readfile()、writefile()、copyfile()、deleterecursively() 四个方法都声明抛出 ioexception。调用方必须显式处理,要么用 try-catch 捕获并记录日志,要么在方法签名上 throws ioexception 向上抛出,交由上层统一处理。例如:
try {
string content = fileutils.readfile("config.json");
// 处理文件内容
} catch (ioexception e) {
log.error("读取配置文件失败", e);
// 返回默认配置或抛出业务异常
}
encryptutils:md5()、sha256() 不抛出受检异常,内部已将 nosuchalgorithmexception 包装为 runtimeexception,调用方无需强制捕获。
threadutils:sleep()、newfixedpool()、executewithtimeout() 均不抛出受检异常,interruptedexception 在内部被恢复中断标志后吞掉,timeoutexception 等被捕获后返回默认值。
2. encryptutils 中 runtimeexception 包装的合理性
hash() 方法捕获 nosuchalgorithmexception 后抛出 runtimeexception("不支持的算法: " + algorithm, e),这种设计是合理的:
- 算法名是编译期常量:
md5、sha-256都是 jdk 内置算法,运行时几乎不可能缺失。若声明为受检异常,会让每个调用方都写无意义的try-catch,徒增样板代码。 - 失败即快速失败:一旦算法确实不存在(如 jdk 版本裁剪),立即抛出运行时异常让程序尽早暴露问题,而不是静默返回错误结果。
- 保留原始异常链:包装时传入原始异常
e作为 cause,排查问题时仍能定位到根因。
3. executewithtimeout 超时后默认值的适用场景与潜在风险
executewithtimeout 在任务超时、被中断或执行异常时都会返回 defaultvalue,这带来便利的同时也隐藏着风险:
- 适用场景:适合「拿不到结果也能继续」的降级场景,例如缓存查询失败时返回空列表、远程调用超时返回预设的兜底配置,保证主流程不被中断。
- 潜在风险:默认值会掩盖真实失败原因。超时、异常、正常返回三种情况在调用方看来结果相同,无法区分。若默认值被当作真实业务数据继续参与计算,可能产生错误结果且难以排查。例如超时返回
0作为订单金额,会导致后续统计失真。 - 建议:对关键业务,不要盲目依赖默认值。可改为抛出异常或返回
optional,让调用方显式感知失败;若必须用默认值,至少记录一条 warn 日志,便于事后追踪。
8. 使用示例与总结
下面是一个综合使用示例,演示如何将这些工具类组合起来完成一个简单的业务场景。
public class demo {
public static void main(string[] args) throws exception {
// 1. 日期处理
system.out.println("当前时间: " + dateutils.now());
// 2. 字符串脱敏
string mobile = "13812345678";
system.out.println("脱敏手机号: " + stringutils.maskmobile(mobile));
// 3. 集合分批处理
list<integer> ids = arrays.aslist(1, 2, 3, 4, 5, 6, 7, 8);
list<list<integer>> batches = collectionutils.partition(ids, 3);
system.out.println("分批结果: " + batches);
// 4. 文件写入与读取
fileutils.writefile("test.txt", "hello java tools!");
system.out.println("文件内容: " + fileutils.readfile("test.txt"));
// 5. 加密
system.out.println("md5: " + encryptutils.md5("hello"));
system.out.println("sha-256: " + encryptutils.sha256("hello"));
// 6. 并发
threadutils.sleep(100);
system.out.println("休眠完成");
}
}
下面是一个更贴近真实业务的综合案例:模拟用户注册流程,综合运用字符串校验、加密、日期记录与集合去重等能力。
import java.time.localdate;
import java.util.arraylist;
import java.util.list;
public class registerdemo {
/** 用户实体 */
static class user {
string mobile;
string password;
localdate registerdate;
user(string mobile, string password, localdate registerdate) {
this.mobile = mobile;
this.password = password;
this.registerdate = registerdate;
}
@override
public string tostring() {
return "user{mobile='" + mobile + "', password='" + password + "', registerdate=" + registerdate + "}";
}
}
public static void main(string[] args) {
// 1. 模拟注册请求参数
string mobile = "13812345678";
string password = "abc123456";
// 2. 使用 stringutils 校验手机号和密码非空
if (stringutils.isblank(mobile) || stringutils.isblank(password)) {
system.out.println("注册失败:手机号和密码不能为空");
return;
}
system.out.println("校验通过:手机号与密码均非空");
// 3. 使用 encryptutils 对密码进行 sha-256 加密
string encryptedpassword = encryptutils.sha256(password);
system.out.println("密码加密结果: " + encryptedpassword);
// 4. 使用 dateutils 记录注册时间
string registertime = dateutils.now();
system.out.println("注册时间: " + registertime);
// 5. 模拟一批用户(含同一天重复注册的账号),使用 collectionutils 按注册日期去重
list<user> userlist = new arraylist<>();
userlist.add(new user("13812345678", encryptedpassword, localdate.now()));
userlist.add(new user("13912345678", encryptedpassword, localdate.now()));
userlist.add(new user("13712345678", encryptedpassword, localdate.now().minusdays(1)));
list<user> distinctusers = collectionutils.distinctbykey(userlist, u -> u.registerdate);
system.out.println("按注册日期去重后的用户数: " + distinctusers.size());
distinctusers.foreach(system.out::println);
}
}
以上工具类覆盖了日常开发中最常见的基础场景。你可以根据项目实际需要继续扩展,比如增加 json 解析、excel 导出、http 请求封装等。希望这套工具类能帮你减少重复劳动,把更多精力放在核心业务逻辑上。
9. 扩展方向
以上工具类只是起点,实际项目中还可以继续封装更多高频能力,下面列出 5 个值得扩展的方向:
- json 解析工具类(基于 jackson):封装对象与 json 字符串之间的序列化、反序列化及格式化输出,适用于接口对接、配置解析等场景。
- http 请求工具类(基于 httpclient):封装 get、post、文件上传等常用请求,统一处理超时、重试与异常,适用于调用第三方 rest 接口。
- excel 导入导出工具类(基于 easyexcel):提供注解驱动的 excel 读写能力,支持大数据量流式处理,适用于报表导出与批量数据导入。
- 正则校验工具类:封装邮箱、手机号、身份证、url 等常见格式的正则校验,统一校验逻辑,适用于表单参数合法性检查。
- 日志打印工具类:封装统一的日志格式与级别控制,支持参数化输出与异常堆栈打印,适用于规范项目日志、便于排查问题。
以上就是6大java开发实用工具类合集:从日期处理到并发控制的详细内容,更多关于java工具类的资料请关注代码网其它相关文章!
发表评论