Duration详解
Java Duration 类:时间计算从此变简单
前言
你有没有遇到过这样的需求:
- 计算两个时间点之间相差多少秒?
- 判断一个任务执行了多长时间?
- 给当前时间加上 2 小时 30 分钟?
- 判断某个操作是否超时(比如超过 5 秒)?
以前用 Date 和 Calendar 处理这些问题,代码写起来又臭又长,还容易出错。自从 Java 8 引入了新的时间 API 后,一切都变简单了。
今天我们就来聊聊 Duration 这个类,它专门用来表示时间段(持续时间),比如"3小时"、“120秒”、"2天"等。掌握了它,时间计算再也不是难题。
第一章:Duration 是什么?
1.1 时间点 vs 时间段
首先要区分两个概念:
-
时间点:某个具体的时刻,比如 “2025年11月13日 15:30:00”
- Java 中用
Instant、LocalDateTime等表示
- Java 中用
-
时间段:一段持续时间,比如 “3小时”、“120秒”
- Java 中用
Duration表示
- Java 中用
打个比方:
- 时间点就像地图上的一个坐标:北京市朝阳区某某街
- 时间段就像两点之间的距离:从 A 点到 B 点相距 5 公里
1.2 Duration 的定义
Duration 是 Java 8 引入的类,用来表示基于时间的时间段。
关键特点:
- 在
java.time包下(不是老的java.util.Date) - 不可变类(immutable),线程安全
- 精度到纳秒(1秒 = 10亿纳秒)
- 内部存储:秒数 + 纳秒数
// Duration 内部结构(简化版)
class Duration {private final long seconds; // 秒数private final int nanos; // 纳秒数(0-999,999,999)
}
1.3 Duration 从哪来?
**JDK 自带!**不需要引入任何第三方库。
import java.time.Duration; // JDK 8+ 自带
要求:JDK 8 或更高版本。如果你的项目还在用 JDK 7,那就只能用老的 Date 和 Calendar 了(或者考虑升级 JDK 😄)。
第二章:创建 Duration 对象
Duration 是不可变类,不能用 new Duration() 创建,必须通过静态工厂方法。
2.1 基于时间单位创建
最常用的几个方法:
import java.time.Duration;// 创建 5 秒
Duration duration1 = Duration.ofSeconds(5);// 创建 3 分钟
Duration duration2 = Duration.ofMinutes(3);// 创建 2 小时
Duration duration3 = Duration.ofHours(2);// 创建 1 天(24小时)
Duration duration4 = Duration.ofDays(1);// 创建 500 毫秒
Duration duration5 = Duration.ofMillis(500);// 创建 1000 纳秒
Duration duration6 = Duration.ofNanos(1000);
完整列表:
| 方法 | 说明 | 示例 |
|---|---|---|
ofNanos(long) | 纳秒 | Duration.ofNanos(1000) |
ofMillis(long) | 毫秒 | Duration.ofMillis(500) |
ofSeconds(long) | 秒 | Duration.ofSeconds(30) |
ofSeconds(long, long) | 秒 + 纳秒 | Duration.ofSeconds(5, 500_000_000) → 5.5秒 |
ofMinutes(long) | 分钟 | Duration.ofMinutes(10) |
ofHours(long) | 小时 | Duration.ofHours(2) |
ofDays(long) | 天 | Duration.ofDays(1) |
2.2 基于字符串创建(ISO-8601 格式)
Duration 支持 ISO-8601 标准的字符串格式:
// 格式:PT[小时]H[分钟]M[秒]S
Duration d1 = Duration.parse("PT2H"); // 2小时
Duration d2 = Duration.parse("PT15M"); // 15分钟
Duration d3 = Duration.parse("PT30S"); // 30秒
Duration d4 = Duration.parse("PT2H30M"); // 2小时30分钟
Duration d5 = Duration.parse("PT1H15M30S"); // 1小时15分30秒// 负数表示"之前"
Duration d6 = Duration.parse("-PT2H"); // -2小时(2小时前)// 天数
Duration d7 = Duration.parse("P2D"); // 2天
Duration d8 = Duration.parse("P2DT3H"); // 2天3小时
格式说明:
P开头表示 Period(时间段)T分隔日期和时间部分D= Days(天)H= Hours(小时)M= Minutes(分钟)S= Seconds(秒)
注意:PT 后面没有日期部分时必须有,P2D 表示 2 天,PT2H 表示 2 小时。
2.3 计算两个时间点之间的 Duration
这是最实用的创建方式!
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Duration;// 方式1:使用 Instant(推荐)
Instant start = Instant.now();
// ... 执行一些操作
Thread.sleep(2000); // 模拟耗时操作
Instant end = Instant.now();Duration duration = Duration.between(start, end);
System.out.println("耗时: " + duration.toMillis() + " 毫秒");// 方式2:使用 LocalDateTime
LocalDateTime dateTime1 = LocalDateTime.of(2025, 11, 13, 10, 0, 0);
LocalDateTime dateTime2 = LocalDateTime.of(2025, 11, 13, 12, 30, 0);Duration duration2 = Duration.between(dateTime1, dateTime2);
System.out.println("相差: " + duration2.toHours() + " 小时"); // 2小时// 方式3:使用 LocalTime(只比较时间部分)
LocalTime time1 = LocalTime.of(10, 30);
LocalTime time2 = LocalTime.of(14, 45);Duration duration3 = Duration.between(time1, time2);
System.out.println("相差: " + duration3.toMinutes() + " 分钟"); // 255分钟
2.4 特殊的 Duration 常量
// 零时长
Duration zero = Duration.ZERO;// 也可以这样创建零时长
Duration zero2 = Duration.ofSeconds(0);
第三章:获取 Duration 的值
创建了 Duration 对象后,怎么获取它的值呢?
3.1 转换为不同单位
Duration duration = Duration.ofHours(2).plusMinutes(30).plusSeconds(45);
// 2小时30分45秒// 转换为不同单位
long days = duration.toDays(); // 0 天(不足1天)
long hours = duration.toHours(); // 2 小时
long minutes = duration.toMinutes(); // 150 分钟
long seconds = duration.toSeconds(); // 9045 秒(2*3600 + 30*60 + 45)
long millis = duration.toMillis(); // 9045000 毫秒
long nanos = duration.toNanos(); // 9045000000000 纳秒System.out.println("总共 " + seconds + " 秒"); // 9045 秒
System.out.println("总共 " + minutes + " 分钟"); // 150 分钟
注意:这些方法返回的是总量,比如 toMinutes() 返回的是总分钟数,不是"小时部分去掉后剩下的分钟"。
3.2 获取组成部分
如果你想要"2小时30分45秒"这样分开的信息:
Duration duration = Duration.ofHours(2).plusMinutes(30).plusSeconds(45);// 获取总秒数
long totalSeconds = duration.getSeconds(); // 9045// 获取纳秒部分(不足1秒的部分)
int nanos = duration.getNano(); // 0(因为没有不足1秒的部分)// 如何分解成"小时:分钟:秒"?需要手动计算
long hours = duration.toHours();
long minutes = duration.toMinutesPart(); // JDK 9+
long seconds = duration.toSecondsPart(); // JDK 9+System.out.println(hours + "小时 " + minutes + "分钟 " + seconds + "秒");
// 输出:2小时 30分钟 45秒
JDK 9+ 新增方法:
| 方法 | 说明 | 返回值范围 |
|---|---|---|
toDaysPart() | 天数部分 | 任意 long |
toHoursPart() | 小时部分 | 0-23 |
toMinutesPart() | 分钟部分 | 0-59 |
toSecondsPart() | 秒部分 | 0-59 |
toMillisPart() | 毫秒部分 | 0-999 |
toNanosPart() | 纳秒部分 | 0-999,999,999 |
JDK 8 中的替代方案:
Duration duration = Duration.ofHours(2).plusMinutes(30).plusSeconds(45);long totalSeconds = duration.getSeconds();
long hours = totalSeconds / 3600;
long minutes = (totalSeconds % 3600) / 60;
long seconds = totalSeconds % 60;System.out.println(hours + ":" + minutes + ":" + seconds);
// 输出:2:30:45
3.3 判断正负和零
Duration positive = Duration.ofHours(2);
Duration negative = Duration.ofHours(-2);
Duration zero = Duration.ZERO;// 判断是否为零
System.out.println(zero.isZero()); // true
System.out.println(positive.isZero()); // false// 判断是否为负数
System.out.println(negative.isNegative()); // true
System.out.println(positive.isNegative()); // false
System.out.println(zero.isNegative()); // false
第四章:Duration 的计算
Duration 提供了丰富的计算方法,而且都是不可变操作(返回新对象,不修改原对象)。
4.1 加法操作
Duration duration = Duration.ofHours(1);// 加秒
Duration d1 = duration.plusSeconds(30); // 1小时30秒// 加分钟
Duration d2 = duration.plusMinutes(15); // 1小时15分钟// 加小时
Duration d3 = duration.plusHours(2); // 3小时// 加天
Duration d4 = duration.plusDays(1); // 25小时// 加毫秒
Duration d5 = duration.plusMillis(500); // 1小时500毫秒// 加纳秒
Duration d6 = duration.plusNanos(1000); // 1小时1000纳秒// 加另一个 Duration
Duration extra = Duration.ofMinutes(30);
Duration d7 = duration.plus(extra); // 1小时30分钟
4.2 减法操作
Duration duration = Duration.ofHours(3);// 减秒
Duration d1 = duration.minusSeconds(30); // 2小时59分30秒// 减分钟
Duration d2 = duration.minusMinutes(15); // 2小时45分钟// 减小时
Duration d3 = duration.minusHours(1); // 2小时// 减天
Duration d4 = duration.minusDays(1); // -21小时(负数!)// 减另一个 Duration
Duration subtract = Duration.ofMinutes(30);
Duration d5 = duration.minus(subtract); // 2小时30分钟
4.3 乘法和除法
Duration duration = Duration.ofHours(2);// 乘法
Duration doubled = duration.multipliedBy(2); // 4小时
Duration tripled = duration.multipliedBy(3); // 6小时// 除法
Duration half = duration.dividedBy(2); // 1小时
Duration third = duration.dividedBy(3); // 40分钟
4.4 取反和绝对值
Duration duration = Duration.ofHours(2);// 取反(正变负,负变正)
Duration negated = duration.negated(); // -2小时Duration negative = Duration.ofHours(-3);
Duration positive = negative.negated(); // 3小时// 绝对值
Duration abs1 = Duration.ofHours(-2).abs(); // 2小时
Duration abs2 = Duration.ofHours(2).abs(); // 2小时
4.5 链式调用(推荐)
由于 Duration 是不可变的,所以可以优雅地链式调用:
Duration duration = Duration.ofHours(1).plusMinutes(30).plusSeconds(45).plusMillis(500);
// 1小时30分45.5秒// 计算复杂时间
Duration workTime = Duration.ofHours(8) // 8小时工作时间.minusMinutes(60) // 减去1小时午休.minusMinutes(30); // 减去30分钟茶歇
// 实际工作时间:6.5小时System.out.println("实际工作: " + workTime.toHours() + "小时"+ workTime.toMinutesPart() + "分钟"); // JDK 9+
第五章:Duration 的比较
5.1 比较大小
Duration d1 = Duration.ofHours(2);
Duration d2 = Duration.ofMinutes(90); // 1.5小时
Duration d3 = Duration.ofHours(2);// 使用 compareTo(返回 -1、0、1)
System.out.println(d1.compareTo(d2)); // 1(d1 > d2)
System.out.println(d2.compareTo(d1)); // -1(d2 < d1)
System.out.println(d1.compareTo(d3)); // 0(d1 == d3)// 更直观的方法(JDK 8+)
System.out.println(d1.equals(d3)); // true
System.out.println(d1.equals(d2)); // false
5.2 判断大小关系
虽然没有直接的 isGreaterThan() 方法,但可以用 compareTo:
Duration d1 = Duration.ofHours(2);
Duration d2 = Duration.ofHours(1);// 判断 d1 是否大于 d2
boolean isGreater = d1.compareTo(d2) > 0; // true// 判断 d1 是否小于 d2
boolean isLess = d1.compareTo(d2) < 0; // false// 判断是否相等
boolean isEqual = d1.compareTo(d2) == 0; // false
工具方法封装(推荐):
public class DurationUtils {public static boolean isGreaterThan(Duration d1, Duration d2) {return d1.compareTo(d2) > 0;}public static boolean isLessThan(Duration d1, Duration d2) {return d1.compareTo(d2) < 0;}
}// 使用
if (DurationUtils.isGreaterThan(actualTime, expectedTime)) {System.out.println("超时了!");
}
第六章:Duration 格式化
6.1 默认的 toString()
Duration d1 = Duration.ofHours(2).plusMinutes(30).plusSeconds(45);
System.out.println(d1.toString()); // PT2H30M45SDuration d2 = Duration.ofDays(1).plusHours(3);
System.out.println(d2.toString()); // PT27H(不会显示为 P1DT3H)
toString() 返回的是 ISO-8601 格式,不太友好。
6.2 自定义格式化
Duration 本身没有提供格式化方法,需要手动处理:
public class DurationFormatter {/*** 格式化为 "HH:MM:SS"*/public static String formatHMS(Duration duration) {long hours = duration.toHours();long minutes = duration.toMinutesPart(); // JDK 9+long seconds = duration.toSecondsPart(); // JDK 9+return String.format("%02d:%02d:%02d", hours, minutes, seconds);}/*** 格式化为中文 "X小时X分钟X秒"*/public static String formatChinese(Duration duration) {long hours = duration.toHours();long minutes = duration.toMinutesPart(); // JDK 9+long seconds = duration.toSecondsPart(); // JDK 9+if (hours > 0) {return hours + "小时" + minutes + "分钟" + seconds + "秒";} else if (minutes > 0) {return minutes + "分钟" + seconds + "秒";} else {return seconds + "秒";}}/*** 格式化为可读的描述*/public static String formatReadable(Duration duration) {long days = duration.toDays();long hours = duration.toHoursPart(); // JDK 9+long minutes = duration.toMinutesPart(); // JDK 9+StringBuilder sb = new StringBuilder();if (days > 0) sb.append(days).append("天");if (hours > 0) sb.append(hours).append("小时");if (minutes > 0) sb.append(minutes).append("分钟");return sb.length() > 0 ? sb.toString() : "0分钟";}
}// 使用
Duration duration = Duration.ofHours(2).plusMinutes(30).plusSeconds(45);System.out.println(DurationFormatter.formatHMS(duration));
// 输出:02:30:45System.out.println(DurationFormatter.formatChinese(duration));
// 输出:2小时30分钟45秒System.out.println(DurationFormatter.formatReadable(duration));
// 输出:2小时30分钟
JDK 8 兼容版本(没有 toXxxPart() 方法):
public static String formatHMS_JDK8(Duration duration) {long totalSeconds = duration.getSeconds();long hours = totalSeconds / 3600;long minutes = (totalSeconds % 3600) / 60;long seconds = totalSeconds % 60;return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
第七章:实战案例
7.1 案例一:计算方法执行时间
import java.time.Duration;
import java.time.Instant;public class PerformanceMonitor {public static void main(String[] args) {Instant start = Instant.now();// 执行业务逻辑processData();Instant end = Instant.now();Duration duration = Duration.between(start, end);System.out.println("方法执行耗时: " + duration.toMillis() + " 毫秒");// 判断是否超时(假设超时阈值是 5 秒)Duration timeout = Duration.ofSeconds(5);if (duration.compareTo(timeout) > 0) {System.out.println("警告:方法执行超时!");}}private static void processData() {try {Thread.sleep(2000); // 模拟耗时操作} catch (InterruptedException e) {e.printStackTrace();}}
}
7.2 案例二:计算会议剩余时间
import java.time.Duration;
import java.time.LocalDateTime;public class MeetingTimer {public static void main(String[] args) {// 会议开始时间LocalDateTime meetingStart = LocalDateTime.of(2025, 11, 13, 14, 0);// 会议时长:1.5小时Duration meetingDuration = Duration.ofMinutes(90);// 会议结束时间LocalDateTime meetingEnd = meetingStart.plus(meetingDuration);// 当前时间LocalDateTime now = LocalDateTime.now();// 计算剩余时间if (now.isBefore(meetingEnd)) {Duration remaining = Duration.between(now, meetingEnd);long minutes = remaining.toMinutes();System.out.println("会议还剩 " + minutes + " 分钟");} else {Duration overdue = Duration.between(meetingEnd, now);long minutes = overdue.toMinutes();System.out.println("会议已超时 " + minutes + " 分钟");}}
}
7.3 案例三:缓存过期时间判断
import java.time.Duration;
import java.time.Instant;public class CacheManager {private Instant cachedTime;private String cachedData;// 缓存有效期:5分钟private static final Duration CACHE_VALIDITY = Duration.ofMinutes(5);public void cache(String data) {this.cachedData = data;this.cachedTime = Instant.now();}public String get() {if (cachedTime == null) {return null; // 没有缓存}// 计算缓存存活时间Duration age = Duration.between(cachedTime, Instant.now());// 判断是否过期if (age.compareTo(CACHE_VALIDITY) > 0) {System.out.println("缓存已过期");cachedData = null;cachedTime = null;return null;}// 显示剩余有效时间Duration remaining = CACHE_VALIDITY.minus(age);System.out.println("缓存还有 " + remaining.toSeconds() + " 秒过期");return cachedData;}public static void main(String[] args) throws InterruptedException {CacheManager cache = new CacheManager();cache.cache("Hello World");Thread.sleep(2000);System.out.println(cache.get()); // 缓存还有 178 秒过期Thread.sleep(300000); // 5分钟后System.out.println(cache.get()); // 缓存已过期}
}
7.4 案例四:计算工作日的工作时长
import java.time.Duration;
import java.time.LocalTime;public class WorkHoursCalculator {public static void main(String[] args) {// 上班时间LocalTime clockIn = LocalTime.of(9, 0);// 下班时间LocalTime clockOut = LocalTime.of(18, 30);// 午休时间Duration lunchBreak = Duration.ofMinutes(60);// 计算总工作时长Duration totalAtWork = Duration.between(clockIn, clockOut);Duration actualWork = totalAtWork.minus(lunchBreak);long hours = actualWork.toHours();long minutes = actualWork.toMinutesPart(); // JDK 9+System.out.println("今天工作了 " + hours + " 小时 " + minutes + " 分钟");// 输出:今天工作了 8 小时 30 分钟// 一周工作时长Duration weeklyWork = actualWork.multipliedBy(5);System.out.println("一周工作 " + weeklyWork.toHours() + " 小时");// 输出:一周工作 42 小时}
}
7.5 案例五:视频播放进度
import java.time.Duration;public class VideoPlayer {private Duration videoDuration; // 视频总时长private Duration currentPosition; // 当前播放位置public VideoPlayer(Duration videoDuration) {this.videoDuration = videoDuration;this.currentPosition = Duration.ZERO;}// 播放指定时长public void play(Duration duration) {currentPosition = currentPosition.plus(duration);if (currentPosition.compareTo(videoDuration) > 0) {currentPosition = videoDuration;}printProgress();}// 快进public void forward(Duration duration) {play(duration);}// 快退public void rewind(Duration duration) {currentPosition = currentPosition.minus(duration);if (currentPosition.isNegative()) {currentPosition = Duration.ZERO;}printProgress();}// 显示进度private void printProgress() {long currentSec = currentPosition.toSeconds();long totalSec = videoDuration.toSeconds();double percentage = (double) currentSec / totalSec * 100;System.out.printf("播放进度: %02d:%02d / %02d:%02d (%.1f%%)%n",currentPosition.toHoursPart(), currentPosition.toMinutesPart(),videoDuration.toHoursPart(), videoDuration.toMinutesPart(),percentage);}public static void main(String[] args) {// 视频总时长 2 小时VideoPlayer player = new VideoPlayer(Duration.ofHours(2));// 播放 30 分钟player.play(Duration.ofMinutes(30)); // 00:30 / 02:00 (25.0%)// 快进 15 分钟player.forward(Duration.ofMinutes(15)); // 00:45 / 02:00 (37.5%)// 快退 10 分钟player.rewind(Duration.ofMinutes(10)); // 00:35 / 02:00 (29.2%)}
}
第八章:Duration vs Period
8.1 Duration 和 Period 的区别
Java 8 引入了两个表示时间段的类,很容易混淆:
| 对比项 | Duration | Period |
|---|---|---|
| 表示 | 基于时间的时间段 | 基于日期的时间段 |
| 单位 | 秒、毫秒、纳秒 | 年、月、日 |
| 精度 | 纳秒级 | 天级 |
| 用途 | 时分秒的计算 | 年月日的计算 |
| 适用场景 | 倒计时、性能监控、时间间隔 | 年龄计算、日期间隔 |
简单记忆:
- Duration:精确到秒的时间段,比如"2小时30分"
- Period:日历上的时间段,比如"3年2个月5天"
8.2 使用对比
import java.time.Duration;
import java.time.Period;
import java.time.LocalDate;
import java.time.LocalDateTime;// Duration:时分秒
Duration duration = Duration.ofHours(2).plusMinutes(30);
System.out.println(duration); // PT2H30M// Period:年月日
Period period = Period.ofYears(1).plusMonths(2).plusDays(15);
System.out.println(period); // P1Y2M15D// Duration:计算两个时间点的间隔(时分秒)
LocalDateTime dt1 = LocalDateTime.of(2025, 11, 13, 10, 0);
LocalDateTime dt2 = LocalDateTime.of(2025, 11, 13, 15, 30);
Duration dur = Duration.between(dt1, dt2);
System.out.println("相差: " + dur.toHours() + " 小时"); // 5 小时// Period:计算两个日期的间隔(年月日)
LocalDate date1 = LocalDate.of(2020, 1, 1);
LocalDate date2 = LocalDate.of(2025, 11, 13);
Period per = Period.between(date1, date2);
System.out.println("相差: " + per.getYears() + " 年 "+ per.getMonths() + " 月 " + per.getDays() + " 天");
// 5 年 10 月 12 天
8.3 何时使用 Duration?
使用 Duration 的场景:
- ✅ 计算方法执行时间
- ✅ 倒计时(精确到秒)
- ✅ 超时判断
- ✅ 性能监控
- ✅ 两个时间点的间隔(小时/分钟/秒)
- ✅ 视频/音频播放进度
使用 Period 的场景:
- ✅ 计算年龄
- ✅ 计算两个日期相差多少年/月/日
- ✅ 订阅周期(1个月、1年等)
- ✅ 日期间隔(不关心具体时间)
第九章:与旧 API 对比
9.1 旧的方式(Date + Calendar)
以前计算两个时间的差值,代码很丑:
import java.util.Date;// 旧方式:使用 Date
Date start = new Date();
// ... 执行操作
Date end = new Date();long diffMillis = end.getTime() - start.getTime(); // 毫秒差
long diffSeconds = diffMillis / 1000; // 秒
long diffMinutes = diffSeconds / 60; // 分钟
long diffHours = diffMinutes / 60; // 小时System.out.println("耗时: " + diffMillis + " 毫秒");
9.2 新的方式(Instant + Duration)
现在简洁多了:
import java.time.Instant;
import java.time.Duration;// 新方式:使用 Instant + Duration
Instant start = Instant.now();
// ... 执行操作
Instant end = Instant.now();Duration duration = Duration.between(start, end);System.out.println("耗时: " + duration.toMillis() + " 毫秒");
System.out.println("耗时: " + duration.toSeconds() + " 秒");
System.out.println("耗时: " + duration.toMinutes() + " 分钟");
优势:
- 代码更简洁
- 类型安全(不会混淆毫秒和秒)
- 方法语义清晰
- 不可变,线程安全
9.3 转换:Date ↔ Instant
如果你的老代码用的是 Date,可以这样转换:
import java.util.Date;
import java.time.Instant;
import java.time.Duration;// Date 转 Instant
Date date = new Date();
Instant instant = date.toInstant();// Instant 转 Date
Instant instant2 = Instant.now();
Date date2 = Date.from(instant2);// 计算两个 Date 之间的 Duration
Date start = new Date();
Thread.sleep(1000);
Date end = new Date();Duration duration = Duration.between(start.toInstant(), end.toInstant());
System.out.println("耗时: " + duration.toMillis() + " 毫秒");
第十章:最佳实践与注意事项
10.1 不可变性
Duration 是不可变的,所有修改方法都返回新对象:
Duration d1 = Duration.ofHours(2);
Duration d2 = d1.plusMinutes(30); // 返回新对象System.out.println(d1.toMinutes()); // 120(原对象未变)
System.out.println(d2.toMinutes()); // 150(新对象)
注意:不要忘了接收返回值!
// 错误:修改不生效
Duration duration = Duration.ofHours(1);
duration.plusMinutes(30); // 返回值被丢弃了!
System.out.println(duration.toMinutes()); // 还是 60// 正确
Duration duration = Duration.ofHours(1);
duration = duration.plusMinutes(30); // 重新赋值
System.out.println(duration.toMinutes()); // 90
10.2 负数 Duration
Duration 可以是负数,表示"之前"的时间:
Duration past = Duration.ofHours(-2); // -2小时
System.out.println(past.isNegative()); // true// 计算时间差可能得到负数
LocalDateTime future = LocalDateTime.now().plusHours(2);
LocalDateTime now = LocalDateTime.now();Duration diff = Duration.between(future, now); // 从未来到现在
System.out.println(diff.isNegative()); // true(负数)// 如果需要绝对值
Duration absolute = diff.abs();
System.out.println(absolute.isNegative()); // false
10.3 精度问题
Duration 的精度是纳秒,但:
// 从 LocalDate 创建 Duration 会丢失精度
LocalDate date1 = LocalDate.of(2025, 11, 13);
LocalDate date2 = LocalDate.of(2025, 11, 14);// 错误:LocalDate 不支持 Duration.between
// Duration d = Duration.between(date1, date2); // 编译错误// 正确:使用 Period
Period period = Period.between(date1, date2);
System.out.println(period.getDays()); // 1
记住:
Duration用于Instant、LocalDateTime、LocalTimePeriod用于LocalDate
10.4 性能考虑
Duration 创建开销很小,可以频繁使用:
// 不用担心性能,Duration 很轻量
for (int i = 0; i < 1_000_000; i++) {Duration d = Duration.ofSeconds(i);// ...
}
但如果需要缓存固定值,可以使用常量:
public class TimeConstants {public static final Duration TIMEOUT_5_SECONDS = Duration.ofSeconds(5);public static final Duration CACHE_TTL = Duration.ofMinutes(30);public static final Duration SESSION_TIMEOUT = Duration.ofHours(2);
}
10.5 与 ChronoUnit 配合
有时候用 ChronoUnit 更方便:
import java.time.temporal.ChronoUnit;// 使用 ChronoUnit
Duration d1 = Duration.of(5, ChronoUnit.SECONDS); // 5秒
Duration d2 = Duration.of(30, ChronoUnit.MINUTES); // 30分钟// 等价于
Duration d3 = Duration.ofSeconds(5);
Duration d4 = Duration.ofMinutes(30);
第十一章:常见问题
Q1: Duration 能表示月和年吗?
不能。Duration 基于秒,无法准确表示"1个月"(因为每月天数不同)。
如果需要月和年,用 Period:
// 错误:Duration 没有 ofMonths
// Duration d = Duration.ofMonths(1); // 编译错误// 正确:使用 Period
Period period = Period.ofMonths(1);
Q2: 如何格式化 Duration 为 “HH:MM:SS”?
Duration 没有内置格式化,需要手动处理(参考第六章):
public static String format(Duration duration) {long hours = duration.toHours();long minutes = duration.toMinutesPart(); // JDK 9+long seconds = duration.toSecondsPart(); // JDK 9+return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
Q3: Duration 和 long 毫秒值哪个好?
推荐 Duration:
// 不推荐:用 long 存毫秒
long timeoutMillis = 5000; // 5秒?5分钟?不明确// 推荐:用 Duration,语义清晰
Duration timeout = Duration.ofSeconds(5);
Q4: 如何判断 Duration 是否超过某个值?
Duration actual = Duration.ofSeconds(10);
Duration limit = Duration.ofSeconds(5);if (actual.compareTo(limit) > 0) {System.out.println("超过限制");
}// 或者封装工具方法
if (actual.toSeconds() > limit.toSeconds()) {System.out.println("超过限制");
}
Q5: Duration 可以序列化吗?
可以,Duration 实现了 Serializable。
JSON 序列化(Jackson):
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule()); // 注册 Java 8 时间模块Duration duration = Duration.ofHours(2);
String json = mapper.writeValueAsString(duration);
System.out.println(json); // "PT2H"Duration parsed = mapper.readValue(json, Duration.class);
第十二章:总结
12.1 核心要点
- Duration 表示时间段:基于秒和纳秒,精确到纳秒级
- 不可变类:所有修改方法返回新对象
- 丰富的创建方法:
ofSeconds()、ofMinutes()、between()等 - 方便的计算:加减乘除、取反、绝对值
- 灵活的转换:转换为秒、分钟、小时等
- 适用场景:性能监控、超时判断、时间间隔计算
12.2 常用方法速查
创建:
Duration.ofSeconds(long)Duration.ofMinutes(long)Duration.ofHours(long)Duration.between(Temporal, Temporal)Duration.parse(String)
获取值:
toSeconds()- 总秒数toMinutes()- 总分钟数toHours()- 总小时数toMillis()- 总毫秒数toSecondsPart()- 秒部分(JDK 9+)toMinutesPart()- 分钟部分(JDK 9+)
计算:
plus(Duration)/plusXxx(long)- 加法minus(Duration)/minusXxx(long)- 减法multipliedBy(long)- 乘法dividedBy(long)- 除法negated()- 取反abs()- 绝对值
比较:
compareTo(Duration)- 比较大小equals(Object)- 判断相等isZero()- 是否为零isNegative()- 是否为负数
12.3 使用建议
- 优先使用 Duration:比 long 毫秒值更清晰
- 使用常量:频繁使用的 Duration 定义为常量
- 注意不可变性:记得接收返回值
- 选对类型:时分秒用 Duration,年月日用 Period
- 链式调用:利用不可变性优雅地链式计算
12.4 下一步
掌握了 Duration 后,可以继续学习:
- Period:处理年月日的时间段
- Instant:时间戳
- LocalDateTime:本地日期时间
- ZonedDateTime:带时区的日期时间
- DateTimeFormatter:日期时间格式化
结语
Duration 是 Java 8 时间 API 的重要组成部分,它让时间计算变得简单而优雅。
以前我们用 System.currentTimeMillis() 计算耗时,代码又长又容易出错。现在有了 Duration,一切都变得如此简单:
Instant start = Instant.now();
// 业务逻辑
Duration duration = Duration.between(start, Instant.now());
System.out.println("耗时: " + duration.toMillis() + "ms");
记住:Duration 是你处理时间间隔的好伙伴。性能监控、超时判断、倒计时、时间计算,有了它都不在话下。
最后送你一句话:时间是宝贵的,但处理时间的代码不应该复杂。用好 Duration,让时间计算变得轻松愉快!
