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。

1 / X