在Java开发中,时间日期处理是每个程序员都会遇到的"必修课"。我见过太多项目因为时间处理不当导致的线上事故——从简单的日期显示错乱,到跨时区同步失败引发的业务逻辑错误。Java 8之前的老Date API就像个布满陷阱的雷区,而Java 8引入的java.time包则带来了全新的处理范式。
老项目中的java.util.Date问题重重:月份从0开始计算、非线程安全、时区处理混乱。我曾接手过一个电商系统,就因为开发人员误用SimpleDateFormat的静态实例,导致促销活动时间显示全乱。迁移到java.time包时要注意:
java复制// 错误示范 - 老API的坑
Date wrongDate = new Date(2023, 1, 1); // 实际是3923年2月1日!
// 正确做法 - 新API
LocalDate correctDate = LocalDate.of(2023, 1, 1);
关键迁移步骤:
跨国项目必须遵守的时区规范:
java复制// 时区转换标准写法
ZonedDateTime utcTime = ZonedDateTime.now(ZoneOffset.UTC);
ZonedDateTime shanghaiTime = utcTime.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
重要提示:永远不要用"GMT+8"这种固定偏移量,应该使用"Asia/Shanghai"这类地区ID,它能自动处理夏令时
计算两个日期差值推荐使用Period和Duration:
java复制LocalDate start = LocalDate.of(2023, 1, 1);
LocalDate end = LocalDate.of(2023, 12, 31);
Period period = Period.between(start, end);
System.out.println(period.getMonths()); // 11个月
处理工作日计算要结合TemporalAdjusters:
java复制LocalDate nextWorkingDay = LocalDate.now()
.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
看这段看似简单的代码:
java复制Integer sum = 0;
for (int i = 0; i < 100000; i++) {
sum += i; // 发生100000次拆箱和装箱
}
通过JMH基准测试,这段代码比直接使用int慢20倍以上。在高性能场景要特别注意:
Integer的valueOf方法有个优化:
java复制Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false
这是因为Integer默认缓存了-128到127的值。这个范围可以通过JVM参数调整:
code复制-XX:AutoBoxCacheMax=1000
包装类比较的三大原则:
java复制Integer x = null;
Integer y = 100;
// 安全比较写法
boolean equal = Objects.equals(x, y);
正则表达式最常见的性能陷阱:
java复制// 错误写法 - 每次调用都重新编译
boolean matched = "abc123".matches("[a-z]+\\d+");
// 正确做法 - 预编译Pattern
private static final Pattern PATTERN = Pattern.compile("[a-z]+\\d+");
boolean matched = PATTERN.matcher("abc123").matches();
在吞吐量大的系统中,预编译可以提高10倍以上的性能。特别要注意:
处理日志文件时,分组提取可以大幅简化代码:
java复制String log = "2023-08-01 14:30:45 [ERROR] System crashed";
Pattern p = Pattern.compile("(\\d{4}-\\d{2}-\\d{2}) (\\d{2}:\\d{2}:\\d{2}) \\[(\\w+)\\] (.+)");
Matcher m = p.matcher(log);
if (m.matches()) {
String date = m.group(1); // 2023-08-01
String level = m.group(3); // ERROR
}
java复制String regex = "^1[3-9]\\d{9}$";
java复制String regex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$";
java复制String regex = "<div[^>]*>(.*?)</div>";
java复制// 错误写法
boolean isLeapYear = year % 4 == 0;
// 正确写法
boolean isLeapYear = Year.of(year).isLeap();
java复制LocalDate lastDay = date.withDayOfMonth(1).plusMonths(1).minusDays(1);
使用Optional处理可能为null的包装类:
java复制Optional.ofNullable(someInteger)
.ifPresentOrElse(
value -> System.out.println(value * 2),
() -> System.out.println("值为空")
);
(a+)+java复制Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
if (!matcher.find(1000)) { // 1秒超时
throw new TimeoutException();
}
java复制String sanitized = input.replaceAll("(\\d{4})\\d{8}(\\d{4})", "$1****$2");
使用Instant配合Long处理时间戳:
java复制// 存储优化
long timestamp = Instant.now().toEpochMilli();
// 读取优化
Instant instant = Instant.ofEpochMilli(timestamp);
Jackson配置建议:
java复制@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
大量使用包装类时考虑:
java复制String regex = """
(?x) # 注释模式
^ # 开头
(\\d{3}-)? # 可选区号
\\d{8} # 主号码
$ # 结尾
""";
java复制@Test
void testPhonePattern() {
assertTrue(Pattern.matches(regex, "13800138000"));
assertFalse(Pattern.matches(regex, "123456"));
}
在金融项目里,我们曾用正则+日期校验防止了数百万的错误交易。有个经典案例:通过(\d{4})(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])验证身份证生日字段,结合LocalDate.parse()的严格模式,拦截了23%的虚假身份信息。