優秀工具包-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