Java常用工具类
Java Utility Classes - Complete Guide
高频工具类与日期时间API的系统掌握课程定位:面向对象编程进阶 - 核心工具类掌握
学习时长:2-3小时(含实践练习)
前置知识:Java基础语法、面向对象概念
学习路径:Math → Random → 旧版Date API → java.time → 其他工具类
核心收获
- 掌握Java核心工具类的使用方法
- 理解日期时间处理的最佳实践
- 提升代码效率和可读性
- 避免常见陷阱和错误用法
引言 - 为什么要掌握工具类?
君子生非异也,善假于物也
—— 荀子《劝学》
为什么学习工具类?
- 效率提升:避免重复造轮子,专注业务逻辑
- 代码质量:经过优化的实现,减少bug风险
- 可维护性:标准API,团队协作无障碍
- 面试必备:开发者的基本素养
实际应用场景
- 数据处理:统计分析、格式转换、验证
- 系统开发:日志记录、缓存管理、调度任务
- 用户界面:时间显示、进度计算、随机效果
- 算法实现:数学运算、排序搜索、模拟仿真
学习原则
知道怎么用
理解设计原理
选择合适工具
避免常见错误
学习目标与技能要求
理论知识目标
工具类认知
- 理解Java工具类的设计理念(静态方法、工具类模式)
- 掌握常用工具类的分类和定位(Math、Random等)
- 了解不同工具类的适用场景和选择原则
日期时间理论
- 理解UTC、时区、夏令时等时间概念
- 掌握ISO 8601时间标准和最佳实践
- 了解旧版API的问题和现代API的优势
实践技能目标
基础应用能力
- Math类:熟练进行数学运算、取整、比较
- Random类:正确生成各种类型的随机数
日期时间处理
- 基础操作:创建、解析、格式化日期时间
- 时间计算:加减操作、间隔计算、比较
- 时区处理:正确进行时区转换
- 迁移能力:将旧代码迁移到现代API
掌握程度分级
入门级
知道常用工具类的存在
能够查文档使用基本方法
熟练级
熟练掌握核心工具类
能够选择合适的工具解决问题
精通级
深入理解设计原理
能够指导他人正确使用
学完本课程,你将能够:
- 独立选择和使用合适的工具类
- 编写高质量、高效率的日期时间处理代码
- 识别和避免常见的API使用陷阱
- 进行旧代码的现代化改造
- 在面试中展示扎实的Java基础
- 为团队制定工具类使用规范
常用工具类总览
Java 提供了大量工具类以简化常见任务,常见于 java.lang 与 java.util 包,通常包含静态方法可直接调用。
- Math:数学运算与常量(
PI、E)。 - Random / ThreadLocalRandom / SplittableRandom:伪随机数生成。
- java.time.*:现代日期时间 API(时区、安全、不可变)。
善用工具类能提升代码质量与开发效率,减少重复造轮子。
数学工具类
Math 类位于 java.lang 包中,无需导入即可直接使用。它提供了丰富的静态方法来执行高级数学运算。
基础运算方法
// 幂运算与根运算
System.out.println(Math.pow(5, 3)); // 125.0
System.out.println(Math.sqrt(16)); // 4.0
System.out.println(Math.cbrt(27)); // 3.0
// 比较运算
Math.abs(-1.5); // 1.5 (绝对值)
Math.max(19, 20); // 20 (最大值)
Math.min(2, 4); // 2 (最小值)
// 取整运算
Math.ceil(4.2); // 5.0 (向上取整)
Math.floor(4.8); // 4.0 (向下取整)
Math.round(4.5); // 5 (四舍五入)
Math.rint(4.5); // 4.0 (就近取整)
高级数学方法
// 三角函数 (参数为弧度)
Math.sin(Math.PI / 2); // 1.0
Math.cos(Math.PI); // -1.0
Math.tan(Math.PI / 4); // 1.0
// 反三角函数
Math.asin(1.0); // π/2
Math.acos(0.0); // π/2
Math.atan(1.0); // π/4
// 对数运算
Math.log(Math.E); // 1.0 (自然对数)
Math.log10(100); // 2.0 (常用对数)
// 指数运算
Math.exp(1); // 2.71828...
Math.expm1(1); // 1.71828... (e^x - 1)
实际应用例题
例题1:距离计算
// 计算两点间距离
double distance(double x1, double y1,
double x2, double y2) {
double dx = x2 - x1;
double dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
}
// 用法:计算(0,0)到(3,4)的距离
double d = distance(0, 0, 3, 4); // 结果: 5.0
例题2:角度转换
// 角度与弧度转换
double toRadians(double degrees) {
return degrees * Math.PI / 180.0;
}
double toDegrees(double radians) {
return radians * 180.0 / Math.PI;
}
// 计算30度角的正弦值
double sin30 = Math.sin(toRadians(30)); // 0.5
例题3:随机数生成
// 生成指定范围的随机整数
int randomBetween(int min, int max) {
return (int)(Math.random() * (max - min + 1)) + min;
}
// 生成1-100的随机数
int rand = randomBetween(1, 100);
例题4:数值格式化
// 保留n位小数
double round(double value, int places) {
double scale = Math.pow(10, places);
return Math.round(value * scale) / scale;
}
// 保留2位小数
double result = round(3.14159, 2); // 3.14
常见陷阱与注意事项
- 浮点精度:
Math.sin(Math.PI)≈ 1.22E-16 而非精确的0 - 整数溢出:
Math.pow(2, 31)结果为2.147E9,注意边界 - NaN处理:无效运算结果为NaN,需要用
Double.isNaN()检查 - 无穷大:除零会产生正负无穷,需要用
Double.isInfinite()检查
随机数生成器
Java提供多种随机数生成器,适用于不同场景:Random、ThreadLocalRandom、SplittableRandom和SecureRandom。
基础Random类
import java.util.Random;
Random r = new Random(); // 默认种子
Random r2 = new Random(12345L); // 固定种子
// 常用方法
boolean bool = r.nextBoolean(); // true/false
byte b = r.nextByte(); // byte范围 [-128,127]
int i = r.nextInt(); // int范围 [-2^31, 2^31-1]
int i2 = r.nextInt(100); // [0,100)
long l = r.nextLong(); // long范围
float f = r.nextFloat(); // [0.0, 1.0)
double d = r.nextDouble(); // [0.0, 1.0)
// 高级用法
r.setSeed(System.currentTimeMillis()); // 重置种子
现代随机类
import java.util.concurrent.ThreadLocalRandom;
import java.util.SplittableRandom;
// ThreadLocalRandom - 多线程环境专用
// 特点:每个线程有自己的实例,无竞争,性能高
ThreadLocalRandom tlr = ThreadLocalRandom.current();
int rand1 = tlr.nextInt(1, 7); // [1,7) - 掷骰子,包含1不包含7
double rand2 = tlr.nextDouble(0.1, 0.9); // [0.1, 0.9) - 指定范围
// 与Random对比:
// Random: 多线程共享一个实例,需要同步,性能差
// ThreadLocalRandom: 每线程独立实例,无需同步,性能优
// SplittableRandom - 并行计算和流式处理专用
// 特点:可分割,支持并行操作,批量生成效率高
SplittableRandom sr = new SplittableRandom();
// 批量生成随机数流 - 高效
int[] batch1 = sr.ints(5, 1, 11).toArray(); // 5个[1,11)整数
double[] batch2 = sr.doubles(100).toArray(); // 100个[0.0,1.0)双精度
// 与其他Random的对比:
// Random: 适合单线程,基础功能
// ThreadLocalRandom: 适合多线程,无锁高性能
// SplittableRandom: 适合并行计算,支持分割和流式API
// 实际应用场景:
// ThreadLocalRandom: Web服务、并发任务中的随机数生成
// SplittableRandom: 大数据并行处理、蒙特卡洛模拟
实际应用场景
🎮 游戏开发
// 游戏掉落系统
class GameLoot {
private static final Random RAND = new Random();
public static int generateDrop(int level) {
// 根据等级生成随机掉落
int baseValue = level * 10;
return RAND.nextInt(baseValue, baseValue * 2);
}
public static boolean criticalHit(double chance) {
return RAND.nextDouble() < chance;
}
}
📊 数据抽样
// 随机抽样算法
import java.util.List;
import java.util.ArrayList;
public List randomSample(List population, int sampleSize) {
SplittableRandom sr = new SplittableRandom();
List sample = new ArrayList<>();
for (int i = 0; i < sampleSize && !population.isEmpty(); i++) {
int index = sr.nextInt(population.size());
sample.add(population.remove(index));
}
return sample;
}
🔐 密码生成
import java.security.SecureRandom;
import java.util.Base64;
public String generateToken(int length) {
// SecureRandom - 密码学安全的随机数生成器
// 特点:
// 1. 使用操作系统提供的熵源(硬件随机、系统状态等)
// 2. 不可预测,无法重现,适合安全场景
// 3. 性能较慢,但安全性高
SecureRandom secure = new SecureRandom();
// 创建指定长度的字节数组
byte[] bytes = new byte[length];
// 使用密码学安全的随机数填充数组
// nextBytes()会生成真正的随机字节,而非伪随机
secure.nextBytes(bytes);
// 使用Base64进行URL安全的编码
// withoutPadding()移除末尾的"="填充字符,更适合URL使用
return Base64.getUrlEncoder().withoutPadding()
.encodeToString(bytes);
// 与普通Random的区别:
// Random: 基于种子+算法,可预测,速度快,适合游戏/模拟
// SecureRandom: 基于熵源,不可预测,速度慢,适合安全场景
// 安全场景使用:
// - 密码生成、盐值、会话ID
// - API密钥、访问令牌
// - 加密密钥、随机挑战码
// - 安全验证码、密码重置令牌
}
最佳实践指南
✓ 推荐做法
- 单线程简单场景:使用
Random - 多线程环境:使用
ThreadLocalRandom - 大量数据生成:使用
SplittableRandom - 安全相关:必须使用
SecureRandom - 测试需要可重现:使用固定种子
- 性能敏感:重用Random实例
✗ 避免做法
- 在并发环境共享
Random实例 - 为每个随机数创建新的Random对象
- 在安全场景使用
Math.random() - 忽略种子的设定需求
- 忽视随机数分布特性
- 在密钥生成中使用伪随机
性能对比
Math.random()
最慢
全局锁
Random
中等
单线程安全
ThreadLocalRandom
较快
无锁并发
SplittableRandom
最快
并行优化
🔍 伪随机概念详解
什么是伪随机?
计算机生成的"随机数"实际上是通过确定算法产生的序列,称为伪随机数。
- 本质:通过数学公式计算得出
- 特点:完全可重现、可预测
- 依赖:初始值(种子)决定整个序列
为什么叫"伪"随机?
// 演示伪随机的确定性
Random r1 = new Random(42); // 固定种子
Random r2 = new Random(42); // 相同种子
System.out.println(r1.nextInt(100)); // 总是输出相同值
System.out.println(r2.nextInt(100)); // 与上面完全相同
// 整个序列都相同!
种子机制原理
// 种子如何影响随机序列
public class SeedDemo {
public static void main(String[] args) {
// 相同种子 = 相同序列
Random r1 = new Random(12345);
Random r2 = new Random(12345);
printSequence(r1, "种子12345 - 序列1");
printSequence(r2, "种子12345 - 序列2");
// 不同种子 = 不同序列
Random r3 = new Random(54321);
printSequence(r3, "种子54321 - 序列3");
}
private static void printSequence(
Random r, String label)
{
System.out.print(label + ": ");
for (int i = 0; i < 5; i++) {
System.out.print(r.nextInt(100) + " ");
}
System.out.println();
}
}
随机数生成器类型对比
真随机数
来源:物理现象
例子:量子噪声、放射性衰变
特点:真正不可预测
用途:密码学、安全领域
伪随机数
来源:算法计算
例子:Random、Math.random()
特点:快速、可重现
用途:游戏、模拟、测试
密码学安全
来源:熵源+算法
例子:SecureRandom
特点:不可预测、高强度
用途:密钥、令牌、密码
硬件随机
来源:硬件设备
例子:RNG芯片、量子生成器
特点:高速度、真随机
用途:高性能安全系统
实际应用中的影响
✅ 可重现性的优势
- 测试调试:固定种子保证结果一致
- 科学模拟:可重复实验验证
- 游戏设计:可控制的随机地图生成
- 机器学习:可复现的训练结果
⚠️ 安全性风险
- 预测风险:攻击者可能预测序列
- 种子泄露:暴露整个随机序列
- 模式识别:长期使用可能发现模式
- 逆向工程:算法可能被破解
常见问题与解决方案
- 随机性不足:使用更好的随机算法或熵源
- 并发竞争:切换到
ThreadLocalRandom - 种子泄露:避免在日志中输出随机序列
- 性能瓶颈:批量生成时使用流式API
- 安全需求:使用
SecureRandom而非普通随机数
旧版日期时间API
Java 8之前的日期时间API存在诸多设计缺陷,了解这些问题有助于避免生产环境中的bug。
核心类与特征
java.util.Date- 可变时间点java.util.Calendar- 可变日期时间java.text.SimpleDateFormat- 非线程安全
主要设计缺陷
- 可变性:对象状态可修改,线程不安全
- 不一致:月份从0开始,年份从1900偏移
- 类型混乱:Date既表示日期又表示时间
- API复杂:Calendar使用方式繁琐
典型陷阱案例
陷阱1:月份从0开始
// 错误:以为是8月,实际是7月
Calendar cal = Calendar.getInstance();
cal.set(2025, 8, 1); // 实际是9月!
// 正确做法
cal.set(2025, Calendar.AUGUST, 1); // 8月
cal.set(2025, 7, 1); // 或者用数字7
陷阱2:可变性导致意外
Date d1 = new Date();
Date d2 = d1; // 引用同一对象
d2.setTime(d2.getTime() + 86400000L);
// d1也被修改了!
陷阱3:SimpleDateFormat线程不安全
// 危险!多线程环境下会出错
public static final SimpleDateFormat SDF =
new SimpleDateFormat("yyyy-MM-dd");
// 线程A调用
String str1 = SDF.format(new Date());
// 线程B同时调用可能导致str1格式错误
String str2 = SDF.format(new Date());
陷阱4:格式符混淆
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat sdf2 = new SimpleDateFormat("YYYY-MM-dd");
Date date = new Date(1472693999999L); // 2016-08-31
// yyyy:标准年,表示我们通常理解的、日历上的年份,从 1 月 1 日到 12 月 31 日
System.out.println(sdf1.format(date)); // 2016-08-31
// YYYY:周年,表示“当前周所属的年份”。这个“周”的定义基于 ISO 8601 标准
System.out.println(sdf2.format(date)); // 2016-08-31
// 跨年日期会错:2022年12月31日,星期六
迁移的紧迫性
- 安全隐患:时间相关的业务逻辑可能出错
- 维护成本:复杂的API增加开发和调试成本
- 性能问题:频繁创建对象影响性能
- 团队协作:新团队成员容易踩坑
Calendar 详细使用与避坑指南
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Date;
public class CalendarUsage {
public static void main(String[] args) {
// 基础创建方式
Calendar cal = Calendar.getInstance(); // 默认时区
Calendar cal2 = new GregorianCalendar(2025, Calendar.OCTOBER, 1);
// 设置日期时间 - 推荐使用常量
cal.set(Calendar.YEAR, 2025);
cal.set(Calendar.MONTH, Calendar.OCTOBER); // 不是10!
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.set(Calendar.HOUR_OF_DAY, 9);
cal.set(Calendar.MINUTE, 30);
cal.set(Calendar.SECOND, 0);
// 一次性设置
cal.set(2025, Calendar.OCTOBER, 1, 9, 30, 0);
// 获取Date对象(可变类型的引用)
Date date = cal.getTime();
System.out.println("初始: " + date);
// 危险:修改Calendar会影响之前的Date引用
cal.add(Calendar.DAY_OF_MONTH, 7); // 告诉 add 方法,你要修改的是日历的“天数”
Date date2 = cal.getTime();
System.out.println("修改后: " + date2);
// 获取字段值
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1; // 记得+1
int day = cal.get(Calendar.DAY_OF_MONTH);
System.out.printf("年: %d, 月: %d, 日: %d%n", year, month, day);
}
}
Calendar常用操作
// 日期运算
cal.add(Calendar.DAY_OF_MONTH, 7); // +7天
cal.add(Calendar.MONTH, -1); // -1月
cal.roll(Calendar.DAY_OF_MONTH, 15); // 滚动,不改变月份
// 清除字段
cal.clear(Calendar.HOUR_OF_DAY);
cal.clear(Calendar.MINUTE);
cal.clear(Calendar.SECOND);
// 比较操作
Calendar now = Calendar.getInstance();
boolean isAfter = cal.after(now);
boolean isBefore = cal.before(now);
// 获取最大值/最小值
int maxDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
int minDay = cal.getActualMinimum(Calendar.DAY_OF_MONTH);
避坑最佳实践
- 防御性复制:如果需要保存Calendar状态,创建副本
- 使用常量:避免使用魔法数字(如8表示月份)
- 线程安全:多线程环境不要共享Calendar实例
- 时区注意:明确指定时区,避免使用系统默认
// 防御性复制
Calendar safe = (Calendar) cal.clone();
// 指定时区
TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");
Calendar localCal = Calendar.getInstance(tz);
Calendar的主要问题
设计问题
- 可变性导致状态难以追踪
- 复杂的API设计,使用繁琐
- 月份从0开始,容易混淆
- Date和Calendar职责不清
实际问题
- 性能开销大(重量级对象)
- 线程安全问题
- 国际化支持有限
- 夏令时处理复杂
更好的java.time.*
Java 8引入的 java.time API重新设计了日期时间处理,解决了旧版API的所有问题。
旧版API问题
- ❌ 可变对象,线程不安全
- ❌ 月份从0开始,容易混淆
- ❌ API设计复杂,使用繁琐
- ❌ 时区处理不完善
- ❌ SimpleDateFormat非线程安全
新版API优势
- ✅ 不可变对象,线程安全
- ✅ 直观的API设计
- ✅ 清晰的类型分离
- ✅ 完善的时区支持
- ✅ 高性能,无全局锁
基础日期时间类
LocalDate- 日期(年月日)LocalTime- 时间(时分秒)LocalDateTime- 日期+时间
时区相关类
Instant- UTC时间戳ZoneId- 时区标识ZonedDateTime- 带时区的日期时间
计算与工具类
Period- 日期间隔Duration- 时间间隔DateTimeFormatter- 格式化器
核心设计原则
不可变性
所有操作都返回新对象,确保线程安全和数据一致性。
清晰的领域模型
明确的类职责:LocalDate处理日期,LocalTime处理时间,避免混淆。
流式API
支持链式调用,代码更加优雅和可读。
默认值合理
月份从1开始,时区使用系统默认,减少配置错误。
基础日期时间类
这些类表示不带时区的日期时间,是最常用的类。所有操作都返回新对象,保持不可变性。
LocalDate 创建
// 当前日期
LocalDate today = LocalDate.now();
// 指定日期
LocalDate date1 = LocalDate.of(2025, 10, 1);
LocalDate date2 = LocalDate.of(2025, Month.OCTOBER, 1);
// 从字符串解析
LocalDate date3 = LocalDate.parse("2025-10-01");
// 从年日
LocalDate date4 = LocalDate.ofYearDay(2025, 274);
LocalTime 创建
// 当前时间
LocalTime now = LocalTime.now();
// 指定时间
LocalTime time1 = LocalTime.of(9, 30);
LocalTime time2 = LocalTime.of(9, 30, 45);
LocalTime time3 = LocalTime.of(9, 30, 45, 123456789);
// 从字符串解析
LocalTime time4 = LocalTime.parse("09:30:45");
// 特殊时间
LocalTime noon = LocalTime.NOON;
LocalTime midnight = LocalTime.MIDNIGHT;
LocalDateTime 创建
// 当前日期时间
LocalDateTime now = LocalDateTime.now();
// 组合创建
LocalDateTime dt1 = LocalDateTime.of(2025, 10, 1, 9, 30);
LocalDateTime dt2 = LocalDateTime.of(date1, time1);
// 从字符串解析
LocalDateTime dt3 = LocalDateTime.parse("2025-10-01T09:30");
// 从LocalDate转换
LocalDateTime dt4 = date1.atTime(9, 30);
LocalDateTime dt5 = date1.atStartOfDay();
常用操作
计算操作(流式API)
LocalDate date = LocalDate.of(2025, 10, 1);
// 加减操作
LocalDate nextWeek = date.plusDays(7);
LocalDate lastMonth = date.minusMonths(1);
// 链式调用
LocalDate result = date
.plusYears(1)
.minusDays(10)
.plusMonths(2);
LocalTime time = LocalTime.of(9, 30);
LocalTime later = time
.plusHours(2)
.plusMinutes(30)
.minusSeconds(15);
调整操作
LocalDate date = LocalDate.of(2025, 10, 1);
// with方法修改字段
LocalDate date2 = date.withYear(2024);
LocalDate date3 = date.withMonth(12);
LocalDate date4 = date.withDayOfMonth(15);
// 使用TemporalAdjusters
LocalDate firstDay = date.withDayOfMonth(1);
LocalDate lastDay = date.withDayOfMonth(date.lengthOfMonth());
LocalDate nextMonday = date.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
LocalDate firstDayOfMonth = date.with(TemporalAdjusters.firstDayOfMonth());
Instant - UTC时间戳
创建与转换
// 当前时间戳(UTC)
Instant now = Instant.now();
// 从毫秒创建
Instant instant1 = Instant.ofEpochMilli(1696118400000L);
// 从秒创建
Instant instant2 = Instant.ofEpochSecond(1696118400L);
// 与LocalDateTime互转
LocalDateTime ldt = LocalDateTime.ofInstant(instant1, ZoneId.systemDefault());
Instant instant3 = ldt.toInstant(ZoneId.systemDefault().getRules().getOffset(ldt));
常用操作
Instant now = Instant.now();
// 时间戳运算
Instant tomorrow = now.plus(1, ChronoUnit.DAYS);
Instant yesterday = now.minus(24, ChronoUnit.HOURS);
// 比较操作
boolean isAfter = now.isAfter(tomorrow);
boolean isBefore = now.isBefore(yesterday);
// 获取字段值
long epochSecond = now.getEpochSecond();
int nano = now.getNano();
实用例1:计算年龄
public static int calculateAge(LocalDate birthDate) {
LocalDate today = LocalDate.now();
return Period.between(birthDate, today).getYears();
}
// 用法
int age = calculateAge(LocalDate.of(1990, 5, 15));
实用例2:工作日计算
public static LocalDate nextWorkDay(LocalDate date) {
LocalDate nextDay = date.plusDays(1);
while (nextDay.getDayOfWeek() == DayOfWeek.SATURDAY ||
nextDay.getDayOfWeek() == DayOfWeek.SUNDAY) {
nextDay = nextDay.plusDays(1);
}
return nextDay;
}
最佳实践建议
- 类型选择:只需要日期用
LocalDate,只需要时间用LocalTime,两者都要用LocalDateTime - 不可变性:利用流式API进行链式调用,代码更加优雅
- 性能考虑:
LocalDate.now()有性能开销,在高频调用场景可以考虑缓存 - 解析验证:使用
parse方法时会自动验证日期时间有效性
时区与 ZonedDateTime
import java.time.*;
public class Zones {
public static void main(String[] args) {
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
ZonedDateTime zdt = ZonedDateTime.of(2025, 10, 1, 9, 0, 0, 0, shanghai);
// 与时间戳互转
Instant instant = zdt.toInstant();
ZonedDateTime utc = instant.atZone(ZoneId.of("UTC"));
System.out.println(zdt + " | " + utc);
}
}
不要手写时区偏移,用标准 ZoneId 避免夏令时陷阱。
Period 与 Duration
import java.time.*;
import java.time.temporal.ChronoUnit;
public class Spans {
public static void main(String[] args) {
Period p = Period.between(LocalDate.of(2025,1,1), LocalDate.of(2025,10,1));
Duration du = Duration.between(LocalTime.NOON, LocalTime.of(18, 30));
// ChronoUnit: 这是一个枚举类,它定义了一套标准的日期时间单位,如 ChronoUnit.DAYS (天)
long days = ChronoUnit.DAYS.between(LocalDate.of(2025,10,1), LocalDate.of(2025,10,10));
System.out.println(p + ", " + du + ", days=" + days);
}
}
Period 面向日期维度;Duration 面向时间维度;差值计算可用 ChronoUnit。
格式化与解析(新)
import java.time.*;
import java.time.format.DateTimeFormatter;
public class Formatting {
public static void main(String[] args) {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dt = LocalDateTime.of(2025, 10, 1, 9, 30);
String s = dt.format(fmt);
LocalDateTime parsed = LocalDateTime.parse(s, fmt);
System.out.println(s + " | " + parsed);
}
}
使用统一格式化器,避免各处自定义字符串格式导致的解析问题。
与旧版 API 互操作
import java.time.*;
import java.util.Date;
public class LegacyInterop {
public static void main(String[] args) {
// Date -> Instant
Instant i = new Date().toInstant();
// Instant -> Date
Date d = Date.from(Instant.now());
}
}
尽量在系统边界进行互转,业务内部统一用 java.time。