优秀工具包-Hutool工具详解
课程概述
Hutool简介
-
定位:
- 小而全的Java工具库,简化开发流程。
- 对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装。
-
核心优势:零依赖、高性能、中文网页完善。
-
应用场景:Web开发、数据处理、加密解密等。
课程目标
- 掌握Hutool核心模块的使用方法
- 理解工具类设计哲学与最佳实践
- 实现常见开发场景的快速编码
核心模块详解
集合操作
集合创建与判空
传统方式:
// 创建集合
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");// 集合判空
if(list == null || list.isEmpty()) {System.out.println("集合为空");
}
Hutool方式:
// 一行代码创建集合
List<String> list = CollUtil.newArrayList("a", "b", "c"); //newHashSet// 安全的判空方法
if(CollUtil.isEmpty(list)) {System.out.println("集合为空");
}
优势:
- 集合初始化代码量减少70%
- 避免NPE风险
- 代码可读性更强
集合分组
[Emp(name=赵六, dept=技术部, age=23)]
[Emp(name=李四, dept=产品部, age=23), Emp(name=王五, dept=产品部, age=23)]
传统方式:
// 分组
Map<String, List<Emp>> groupedMap = new HashMap<>();
for (Emp emp : employees) {String dept = emp.getDept();if (!groupedMap.containsKey(dept)) {groupedMap.put(dept, new ArrayList<>());}groupedMap.get(dept).add(emp);
}
// 转换
List<List<Emp>> list = new ArrayList<>();
for (List<Emp> empList : groupedMap.values()) {list.add(empList);
}
Hutool方式:
List<List<Emp>> list = CollUtil.groupByField(employees, "dept");
优势:
- 代码量减少80%
- 避免手动处理分组逻辑
- 支持按字段名分组
集合过滤
传统方式:
// 过滤
List<Emp> filtered = new ArrayList<>();
for (Emp emp : employees) {if (emp.getAge() > 30 && "研发部".equals(emp.getDept())) {filtered.add(emp);}
}
Hutool方式:
List<Emp> filtered = CollUtil.filter(employees, emp -> emp.getAge() > 30 && "研发部".equals(emp.getDept()));
优势:
- 使用Lambda表达式更简洁
- 链式调用更流畅
- 可读性更好
IO流 & 文件操作
文件读取
传统方式:
/*** 读取文件所有行内容(传统JDK方式)* @param filePath 文件路径* @return 行内容列表*/
public List<String> readFileTraditional(String filePath) {List<String> lines = new ArrayList<>();BufferedReader br = null;try {// 1. 创建缓冲读取器br = new BufferedReader(new FileReader(filePath));String line;// 2. 按行读取并放入集合while ((line = br.readLine()) != null) {lines.add(line);}} catch (IOException e) {// 4. 异常处理System.err.println("读取文件失败: " + e.getMessage());e.printStackTrace();} finally {// 5. 关闭资源(需要嵌套try-catch)try {if (br != null){br.close();}} catch (IOException e) {System.err.println("关闭流失败: " + e.getMessage());}}return lines;
}
传统方式的问题:
- 需要手动管理流对象
- 异常处理代码占整体代码量的50%以上
- 资源关闭需要嵌套try-catch,容易遗漏
- 不支持指定字符编码(默认使用系统编码)
- 代码行数20多,实际业务逻辑只有5行
Hutool方式:
/*** 读取文件所有行内容(Hutool方式)* @param filePath 文件路径* @return 行内容列表*/
public List<String> readFileByHutool(String filePath) {// 指定UTF-8编码读取(自动关闭资源)return FileUtil.readLines(filePath, CharsetUtil.UTF_8);
}
优势:
- 代码量减少70%
- 指定字符编码更简单
- 自动关闭资源
文件拷贝
传统方式:
try (InputStream in = new FileInputStream("source.txt");OutputStream out = new FileOutputStream("target.txt")) {byte[] buffer = new byte[1024];int length;while ((length = in.read(buffer)) != -1) {out.write(buffer, 0, length);}
} catch (IOException e) {e.printStackTrace();
}
Hutool方式:
FileUtil.copy("source.txt", "target.txt", true);// true 如果目标文件已存在,则覆盖它
优势:
- 代码量减少90%
- 自动处理资源关闭
- 支持覆盖选项
关闭流
传统方式:
try {if (stream != null) {stream.close();}
} catch (IOException e) {e.printStackTrace();
}
Hutool方式:
IoUtil.close(stream);
如果用上面传统方式的编码进行关闭流的实现,当第三方机构对代码进行安全扫描时,就不能通过。
流转换为字符串
传统方式:
// 将inputStream流中的内容读取出来并使用String接收
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))
){String line;while ((line = reader.readLine()) != null) {sb.append(line);}
} catch (IOException e) {e.printStackTrace();
}
String content = sb.toString();
Hutool方式:
String content = IoUtil.read(inputStream, StandardCharsets.UTF_8);
优势:
- 代码量减少80%
- 自动处理字符编码
- 自动关闭资源
JDK8时间API增强
日期格式化与解析
传统方式:
// 格式化
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDate = now.format(formatter); // "2025-08-09 14:30:45"// 解析
String dateStr = "2025-08-09 14:30:45";
LocalDateTime parsedDate = LocalDateTime.parse(dateStr, formatter);
Huto增强方式:
// 格式化
String str = LocalDateTimeUtil.format( LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss");
// 或使用预定义格式
String str = LocalDateTimeUtil.format(now, DatePattern.NORM_DATETIME_PATTERN);// 解析
LocalDateTime date = LocalDateTimeUtil.parse("2023-08-20 14:30:45","yyyy-MM-dd HH:mm:ss");
优势:
- 内置线程安全的格式化器缓存
- 支持
DatePattern
中所有标准格式 - 更简洁的API
日期比较
传统方式:
LocalDateTime startTime = LocalDateTime.of(2025,8,1,0,0,0);
LocalDateTime endTime = LocalDateTime.of(2025,8,30,23,59,59);
LocalDateTime now = LocalDateTime.now();// 判断当前时间是否在指定的区间内
boolean isInRange = now.isAfter(startTime) && now.isBefore(endTime);
Hutool增强方式
boolean isIn = LocalDateTimeUtil.isIn(now, start, end); // 是否在区间内
日期范围
传统方式:
// 获取当天开始时间(2025-08-10 00:00:00)
LocalDateTime todayStart = LocalDateTime.now() // 当前时间.truncatedTo(ChronoUnit.DAYS); // 截断到天
// 获取当天结束时间(2025-08-10 23:59:59)
LocalDateTime todayEnd = LocalDateTime.now().truncatedTo(ChronoUnit.DAYS) // 截断到天.plusDays(1) // 加一天.minusNanos(1000000); // 减去一毫秒(1000000纳秒)
Hutool增强方式:
// 获取当天的起始时间
LocalDateTime dayStart = LocalDateTimeUtil.beginOfDay(LocalDate.now()); // 5.8.28版本
LocalDateTime dayStart = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
// 获取当天结束时间
LocalDateTime dayEnd = LocalDateTimeUtil.endOfDay(LocalDateTime.now());
扩展:
Date 转 LocalDateTime
Date date = new Date();
LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();// Hutool写法
LocalDateTime localDateTime = DateUtil.toLocalDateTime(date);
Date 转 LocalDate
Date date = new Date();
LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
Date转LocalTime
Date date = new Date();
// 转换为 LocalDateTime
LocalTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().toLocalTime();
LocalDateTime 转 LocalDate
LocalDateTime localDateTime = LocalDateTime.now();
LocalDate localDate = localDateTime.toLocalDate();
序列化操作
JSON序列化与反序列化
传统方式(Jackson):
若完全基于Jdk提供的原生API去实现序列化,生成的数据是二进制格式,不是人类可读的JSON格式。这里不做展示。
Hutool方式:
// 将Student对象转成json字符串
String json = JSONUtil.toJsonStr(student);
// 将json字符串转成Student对象
Student student = JSONUtil.toBean(json, Student.class);// 将List<Student>转成json字符串
ArrayList<Student> students = CollUtil.newArrayList(new Student("张三", 18, List.of("java", "python", "c++")),new Student("李四", 18, List.of("java", "python", "c++")),new Student("王五", 18, List.of("java", "python", "c++")));
String studentsStr = JSONUtil.toJsonStr(students);
// 将json字符串转成List<Student>
List<Student> list = JSONUtil.toList(studentsStr, Student.class);
序列化:将对象转换为字符串。
反序列化:将字符串转换为对象。
优势:
-
代码更简洁
-
无需手动处理异常和类型转换
-
不需要实现Serializable
扩展:
其他序列化工具:
- Jackson
- 组织:com.fasterxml.jackson.core
- 维护者:FasterXML 公司
- 是 Spring Boot 默认的 JSON 处理库
- Fastjson
- 组织:com.alibaba
- 维护者:阿里巴巴
- 是阿里巴巴开源的 JSON 处理库
字符串操作
字符串格式化
传统方式:
String name = "lisi";
int age = 23;
double score = 98.5;String s = String.format("姓名:%s,年龄:%d,分数:%.2f", name, age,score);
Hutool方式:
StrUtil.format("姓名:{},年龄:{},分数:{}", name, age, score);
优势:
- 不需要记忆各种格式化符号
- 统一的占位符语法,更简洁易懂
- 减少格式化错误的可能性
字符串判空
传统方式:
String str = " ";
boolean isEmpty = str == null || str.trim().isEmpty();
Hutool方式:
boolean isBlank = StrUtil.isBlank(" ");
优势:
- 自动处理null值
- 自动去除空白字符判断
- 代码简洁明了
严格空判断
传统方式:
String str = "";
boolean isEmpty = str == null || str.isEmpty();
Hutool方式:
boolean isEmpty = StrUtil.isEmpty("");
优势:
- 严格判断空字符串(不包括空白字符)
- 自动处理null值
- 方法名语义更明确
与isBlank区别:
StrUtil.isEmpty(" "); // false
StrUtil.isBlank(" "); // true
安全字符串比较
传统方式:
String a = null;
String b = "test";
boolean eq = a != null && a.equals(b); // a.equals(b)可能NullPointerException
Hutool方式:
boolean eq = StrUtil.equals(null, "test"); // false
优势:
- 完全避免NullPointerException
- 可以比较null值
- 语义更明确
Bean操作
对象属性拷贝
传统方式:
// 需要手动拷贝每个属性
Student student = new Student("张三", 18, List.of("java", "python", "c++"));
Student student1 = new Student();
student1.setName(student.getName());
student1.setAge(student.getAge());
student1.setLikes(student.getLikes());
System.out.println(student1);//若需要忽略某些属性、处理 null 值、转换日期格式等,需要手动编写大量逻辑,代码复用性差。
Hutool方式:
// 将源对象的属性值复制到目标对象中 (目标对象要存在)
BeanUtil.copyProperties(source, target);// 根据源对象创建目标类的实例,并复制属性值,返回新对象 (目标对象不存在)
User target = BeanUtil.copyProperties(source, Student.class);// 忽略null值 + 忽略指定属性 + 日期格式转换 5.7.0 +版有
CopyOptions options = CopyOptions.create().setIgnoreNullValue(true) // 忽略null值.setIgnoreProperties("id") // 忽略id属性.setDatePattern("yyyy-MM-dd"); // 日期格式化
BeanUtil.copyProperties(source, target, options);
优势:
- 代码简洁
- 自动拷贝同名属性
- 支持不同类型转换
- 复杂格式复用性强
对象与 Map 互转
传统实现:
// 原生方式:Bean转Map
Map<String, Object> map = new HashMap<>();
map.put("name", user.getName());
map.put("age", user.getAge());
// ... 每个属性都要手动put// 原生方式:Map转Bean
User user = new User();
user.setName(map.get("name"));
user.setAge(Integer.parseInt(map.get("age")));
// ... 每个属性都要手动get
Hutool方式:
// Bean转Map
Map<String, Object> map = BeanUtil.beanToMap(user);// Map转Bean
User user = BeanUtil.mapToBean(map, User.class, true); // true 是否转换为驼峰命名
优势:
- 自动完成属性与键值的映射,
- 嵌套属性
- 类型自动转换
进阶功能与设计思想
设计哲学
- 链式调用:如
StrUtil.builder().append().toString()
- 智能默认值:避免空指针(如
Convert.toInt(null, 0)
) - 低侵入性:静态方法封装,零配置集成
性能优化技巧
- 复用工具实例(如
SecureUtil.aes(key)
生成单例) - 大文件处理:流式读写避免内存溢出
扩展能力
- 自定义工具类继承(如扩展
XXXUtil
) - 模块化引入(按需选择
hutool-core
或全量依赖)
实战案例
环境准备
在开始前,请确保项目中已引入Hutool核心依赖:
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.39</version>
</dependency>
案例1:RandomUtil工具 - 随机数据生成专家
功能特性
- 多样化生成:支持随机数、字符串、日期、中文名等
- 安全可靠:基于加密算法的随机数生成
- 灵活配置:可自定义长度、范围、字符集
典型应用场景
-
双色球号码生成
通过
randomEle()
方法实现彩票系统随机选号:// 生成红球(1-33选6)和蓝球(1-16选1) String redBalls = RandomUtil.randomEle(getRedBalls()/*获取红球的列表*/, 6).stream().map(i -> String.format("%02d", i))/*格式化*/.collect(Collectors.joining(" ")); String blueBall = String.valueOf(RandomUtil.randomInt(1, 16)); System.out.println("双色球号码:" + redBalls + " + " + blueBall);
-
企业级数据脱敏
生成随机测试数据替代敏感信息:
// 生成随机中文姓名 String chineseName = RandomUtil.randomChineseName();//用RandomUtil.randomChinese()实现单个随机中文字符 // 生成随机公司名称 String companyName = RandomUtil.randomStringUpper(5); // 5位含大写字母
-
分布式ID生成
结合时间戳生成唯一ID:
long timestamp = System.currentTimeMillis(); String uniqueId = timestamp + "-" + RandomUtil.randomString(6);
案例2:TreeUtil工具 - 树形结构构建大师
功能特性
- 零递归构建:自动处理父子关系
- 灵活配置:支持自定义节点属性映射
- 高效转换:数据库数据→树形结构一键转换
典型应用场景
-
部门组织架构展示
将扁平化数据转换为树形层级:
// 定义节点实体(需继承TreeNode) @Data public class DeptNode extends TreeNode<Integer> {private String deptName;private String manager; }// 构建树形结构 List<DeptNode> deptList = getDataFromDB(); // 从数据库获取数据 List<Tree<Integer>> tree = TreeUtil.build(deptList, 0, config -> {config.setIdKey("id").setParentIdKey("parentId").setNameKey("deptName").putExtra("manager", node -> getManagerName(node.getId())); });
-
菜单权限管理系统
我们假设要构建一个菜单,可以实现系统管理和店铺管理,菜单的样子如下:
系统管理|- 用户管理|- 添加用户店铺管理|- 商品管理|- 添加商品
那这种结构如何保存在数据库中呢?一般是这样的:
id parentId name weight 1 0 系统管理 5 11 1 用户管理 10 111 1 用户添加 11 2 0 店铺管理 5 21 2 商品管理 10 221 2 添加添加 11 动态生成前端菜单树:
// 构建node列表 List<TreeNode<String>> nodeList = CollUtil.newArrayList();nodeList.add(new TreeNode<>("1", "0", "系统管理", 5)); nodeList.add(new TreeNode<>("11", "1", "用户管理", 222222)); nodeList.add(new TreeNode<>("111", "11", "用户添加", 0)); nodeList.add(new TreeNode<>("2", "0", "店铺管理", 1)); nodeList.add(new TreeNode<>("21", "2", "商品管理", 44)); nodeList.add(new TreeNode<>("221", "2", "商品管理2", 2));// 0表示最顶层的id是0 List<Tree<String>> treeList = TreeUtil.build(nodeList, "0");//配置 TreeNodeConfig treeNodeConfig = new TreeNodeConfig(); // 自定义属性名 都要默认值的 treeNodeConfig.setWeightKey("order"); treeNodeConfig.setIdKey("rid"); // 最大递归深度 treeNodeConfig.setDeep(3);//转换器 List<Tree<String>> treeNodes = TreeUtil.build(nodeList, "0", treeNodeConfig,(treeNode, tree) -> {tree.setId(treeNode.getId());tree.setParentId(treeNode.getParentId());tree.setWeight(treeNode.getWeight());tree.setName(treeNode.getName());// 扩展属性 ...tree.putExtra("extraField", 666);tree.putExtra("other", new Object());});
若依的菜单权限管理实现与其类似。
-
商品分类树形展示
支持多级分类嵌套:
// 构建带扩展属性的分类树 List<Category> categories = categoryService.findAll(); List<Tree<Long>> categoryTree = TreeUtil.build(categories, 0L, config -> {config.setIdKey("id").setParentIdKey("parentId").setNameKey("categoryName").putExtra("imageUrl", node -> getImageUrl(node.getType())); });
学习资源与延伸
其他的api,大家可翻阅官网代码示例和Api文档进行学习。
Hutool中文官网:https://hutool.cn/
api文档:https://plus.hutool.cn/apidocs/
GitHub仓库:looly/hutool