1. 为什么需要整理Java工具类?
在Java开发中,工具类(Utility Class)就像我们日常生活中的瑞士军刀,它们封装了各种常用功能,让我们避免重复造轮子。我见过太多项目因为缺乏统一的工具类管理,导致同一个功能被不同开发人员反复实现,最终维护成本极高。
工具类通常具有以下特征:
- 包含静态方法为主
- 禁止实例化(私有构造器)
- 功能单一且独立
- 无状态(不保存实例变量)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心工具类分类与实现
2.1 字符串处理工具
字符串操作是Java开发中最频繁的需求之一。Apache Commons Lang的StringUtils是最著名的工具类,但我建议根据项目需求封装自己的StringUtil:
java复制public class StringUtil {
private StringUtil() {}
// 高效判空
public static boolean isBlank(CharSequence cs) {
int strLen = cs == null ? 0 : cs.length();
if (strLen == 0) return true;
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
return true;
}
// 驼峰转下划线
public static String camelToUnderline(String str) {
if (isBlank(str)) return str;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (Character.isUpperCase(c)) {
sb.append("_").append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
}
注意:字符串拼接优先使用StringBuilder,在循环体内尤其重要。我见过因使用"+"拼接字符串导致性能下降10倍的案例。
2.2 日期时间处理
Java 8的java.time包已经很好用,但实际项目中我们仍需要封装常用操作:
java复制public class DateUtil {
private static final DateTimeFormatter DEFAULT_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static String formatNow() {
return LocalDateTime.now().format(DEFAULT_FORMATTER);
}
public static long betweenDays(LocalDate start, LocalDate end) {
return ChronoUnit.DAYS.between(start, end);
}
// 时区安全转换
public static ZonedDateTime toZone(LocalDateTime local, String zoneId) {
return local.atZone(ZoneId.systemDefault())
.withZoneSameInstant(ZoneId.of(zoneId));
}
}
常见坑点:
- SimpleDateFormat线程不安全(必须每次new或ThreadLocal)
- 时区问题(务必明确指定ZoneId)
- 月份从0开始(Calendar的遗留问题)
2.3 集合工具类
Guava和Apache Commons Collections提供了强大的集合工具,但核心方法可以自己实现:
java复制public class CollectionUtil {
// 安全获取集合元素
public static <T> T getSafe(List<T> list, int index, T defaultValue) {
return index >= 0 && index < list.size() ? list.get(index) : defaultValue;
}
// 集合分页
public static <T> List<T> page(List<T> list, int page, int size) {
if (list == null || list.isEmpty()) {
return Collections.emptyList();
}
int fromIndex = (page - 1) * size;
if (fromIndex >= list.size()) {
return Collections.emptyList();
}
int toIndex = Math.min(fromIndex + size, list.size());
return list.subList(fromIndex, toIndex);
}
}
性能提示:
- ArrayList的subList是视图,修改会影响原列表
- 大数据量分页建议使用数据库分页
- 频繁contains操作考虑使用Set
3. 高级工具类设计
3.1 缓存工具实现
基于ConcurrentHashMap实现简单的LRU缓存:
java复制public class CacheUtil<K, V> {
private final ConcurrentHashMap<K, V> map;
private final LinkedBlockingDeque<K> queue;
private final int maxSize;
public CacheUtil(int maxSize) {
this.maxSize = maxSize;
this.map = new ConcurrentHashMap<>(maxSize);
this.queue = new LinkedBlockingDeque<>(maxSize);
}
public void put(K key, V value) {
synchronized (this) {
if (map.containsKey(key)) {
queue.remove(key);
} else if (queue.size() >= maxSize) {
K oldest = queue.poll();
map.remove(oldest);
}
map.put(key, value);
queue.offer(key);
}
}
public V get(K key) {
synchronized (this) {
if (map.containsKey(key)) {
queue.remove(key);
queue.offer(key);
return map.get(key);
}
return null;
}
}
}
3.2 反射工具类
反射虽然影响性能,但在框架开发中必不可少:
java复制public class ReflectUtil {
// 获取字段值(包括私有字段)
public static Object getFieldValue(Object obj, String fieldName) {
try {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 执行私有方法
public static Object invokeMethod(Object obj, String methodName,
Class<?>[] paramTypes, Object[] args) {
try {
Method method = obj.getClass().getDeclaredMethod(methodName, paramTypes);
method.setAccessible(true);
return method.invoke(obj, args);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
警告:反射会破坏封装性,且性能较差(比直接调用慢50-100倍),仅应在框架开发等特殊场景使用。
4. 工具类的最佳实践
4.1 设计原则
- 单一职责:一个工具类只做一类事情(如DateUtil只处理日期)
- 无状态:避免使用实例变量,所有方法应该是静态的
- 防御性编程:对null参数做合理处理(返回默认值或抛异常)
- 性能考量:高频使用的方法要做性能优化
- 文档完善:每个公共方法都应该有清晰的JavaDoc
4.2 常见问题解决方案
问题1:工具类方法太多怎么办?
- 按功能拆分子类(如StringUtil拆分为StringFormatUtil、StringValidateUtil)
- 使用静态导入简化调用:
import static com.xxx.util.StringUtil.*;
问题2:第三方工具类冲突怎么办?
- 封装适配层(如MyStringUtils内部调用StringUtils)
- 使用maven shade插件重命名包
问题3:如何保证线程安全?
- 避免共享可变状态
- 使用ThreadLocal保存非线程安全对象(如SimpleDateFormat)
- 对必要的同步操作使用synchronized或Lock
5. 现代Java工具类演进
5.1 函数式编程工具
Java 8引入的Stream API和函数式接口可以创建更现代的工具类:
java复制public class StreamUtil {
// 安全的Stream转换
public static <T> Stream<T> ofNullable(Collection<T> coll) {
return coll == null ? Stream.empty() : coll.stream();
}
// 批量处理
public static <T> void batchProcess(Collection<T> coll,
Consumer<T> processor, int batchSize) {
ofNullable(coll)
.collect(Collectors.groupingBy(e -> e.hashCode() % batchSize))
.values()
.parallelStream()
.forEach(batch -> batch.forEach(processor));
}
}
5.2 响应式编程工具
对于使用Reactor或RxJava的项目:
java复制public class ReactorUtil {
// 带超时的响应式调用
public static <T> Mono<T> withTimeout(Mono<T> mono, Duration timeout, T fallback) {
return mono.timeout(timeout)
.onErrorResume(TimeoutException.class, e -> Mono.just(fallback));
}
// 重试策略
public static <T> Mono<T> withRetry(Mono<T> mono, int maxAttempts,
Duration delay) {
return mono.retryWhen(Retry.backoff(maxAttempts, delay)
.filter(e -> e instanceof TimeoutException));
}
}
6. 工具类测试要点
好的工具类必须有完善的单元测试:
java复制class StringUtilTest {
@Test
void isBlank_shouldReturnTrueForNull() {
assertTrue(StringUtil.isBlank(null));
}
@ParameterizedTest
@ValueSource(strings = {"", " ", "\t", "\n"})
void isBlank_shouldReturnTrueForBlankStrings(String input) {
assertTrue(StringUtil.isBlank(input));
}
@Test
void camelToUnderline_shouldConvertCorrectly() {
assertEquals("user_name", StringUtil.camelToUnderline("userName"));
assertEquals("order_id", StringUtil.camelToUnderline("orderId"));
}
}
测试建议:
- 覆盖边界条件(null、空字符串、极值等)
- 参数化测试减少重复代码
- 性能测试(特别是高频调用的工具方法)
7. 个人工具类库建设
经过多年实践,我建议按以下结构组织项目中的工具类:
code复制src/main/java
└── com
└── xxx
└── util
├── annotation # 注解工具
├── collection # 集合工具
├── date # 日期工具
├── io # IO工具
├── json # JSON处理
├── math # 数学计算
├── reflect # 反射工具
├── security # 安全相关
├── string # 字符串处理
└── validator # 验证工具
每个子包可以发布为独立模块,通过maven或gradle按需引入。我通常会维护一个core-util包含最基础的工具,其他工具按领域拆分。
