# Java开发者必知:LocalDateTime与BigDecimal实战指南 在Java日常开发中,日期时间处理与高精度数值运算始终是绕不开的核心场景。无论你使用Python、C++、TypeScript还是Go,都会遇到类似问题,而Java生态中`LocalDateTime`与`BigDecimal`正是解决这两大痛点的利器。本文将结合实际案例,帮你彻底掌握这两个工具类的正确用法。 ## 一、日期时间处理:为何从Date迁移到LocalDateTime? 早期Java版本中的`Date`类长期被开发者诟病——方法过时、线程不安全、时区处理繁琐。Java 8引入的`LocalDateTime`彻底改变了这一局面,它**不可变、线程安全**,整合了日期与时间两大概念,成为当前Java开发中的标准方案。 ### 1.1 老牌Date类:了解历史,谨慎使用 `Date`类位于`java.util`包,可精确到毫秒。但它存在几个明显缺陷: - ⚠️ 构造方法存在偏移问题:年份从1900起算,月份从0开始(0代表1月) - ⚠️ 多数获取字段的方法已过时,仅`compareTo`、`getTime`等少数方法仍在使用 - ⚠️ 非线程安全,设计上存在诸多不友好之处 虽然在新项目中不推荐使用,但维护遗留代码时仍需理解其基本用法。下面通过示例来了解:
import java.util.Date;
public class DateDemo {
    public static void main(String[] args) {
        // 1. 获取当前系统时间,精确到毫秒
        Date nowDate = new Date();
        System.out.println("当前系统时间:" + nowDate);
        // 2. 构造指定时间(注意偏移:124=2024-1900,1=2月,10=10日)
        Date specifyDate = new Date(124, 1, 10, 15, 30, 20);
        System.out.println("指定时间(2024-02-10 15:30:20):" + specifyDate);
        // 3. 日期比较:after/before/compareTo
        boolean isAfter = nowDate.after(specifyDate);
        boolean isBefore = nowDate.before(specifyDate);
        int compare = nowDate.compareTo(specifyDate);
        System.out.println("当前时间是否在指定时间之后:" + isAfter);
        System.out.println("当前时间是否在指定时间之前:" + isBefore);
        System.out.println("日期比较结果(0=相等,1=大于,-1=小于):" + compare);
    }
}
输出结果如下(随系统时间变化):
当前系统时间:Sat Mar 21 16:00:00 CST 2026
指定时间(2024-02-10 15:30:20):Sat Feb 10 15:30:20 CST 2024
当前时间是否在指定时间之后:true
当前时间是否在指定时间之前:false
日期比较结果(0=相等,1=大于,-1=小于):1
### 1.2 LocalDateTime:Java 8+日期处理首选方案 ✅ `LocalDateTime`位于`java.time`包,是**不可变且线程安全**的日期时间类。它整合了`LocalDate`(日期)与`LocalTime`(时间),提供创建、解析、字段获取、运算、调整等全套操作,是目前Java开发的**核心日期类**。 #### 1.2.1 创建LocalDateTime对象的三种方式 由于构造方法为私有,无法直接`new`,必须通过静态方法创建。核心方式包括: - `now()`:获取当前系统时间 - `of()`:指定年月日时分秒 - `parse()`:从字符串解析(需配合`DateTimeFormatter`指定格式) ⚠️ 注意格式符号大小写敏感:`yyyy`=年、`MM`=月、`dd`=日、`HH`=24小时制、`hh`=12小时制。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class LocalDateTimeCreate {
    public static void main(String[] args) {
        // 方式1:获取当前系统日期时间(最常用)
        LocalDateTime now = LocalDateTime.now();
        System.out.println("当前日期时间:" + now);
        // 方式2:指定年/月/日/时/分/秒创建
        LocalDateTime specifyTime = LocalDateTime.of(2026, 3, 21, 16, 0, 0);
        System.out.println("指定日期时间:" + specifyTime);
        // 方式3:从自定义格式的字符串解析(需指定DateTimeFormatter)
        String timeStr = "2026-03-21 16:05:30";
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime parseTime = LocalDateTime.parse(timeStr, formatter);
        System.out.println("字符串解析的日期时间:" + parseTime);
    }
}
运行结果:
当前日期时间:2026-03-21T16:00:00.123456789
指定日期时间:2026-03-21T16:00
字符串解析的日期时间:2026-03-21T16:05:30
#### 1.2.2 获取日期时间字段 通过`get`系列方法可以直接获取年、月、日、时、分、秒、星期等字段,**无偏移问题**,代码可读性远胜`Date`类。
import java.time.LocalDateTime;
public class LocalDateTimeGet {
    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        // 基础字段:年、月、日、时、分、秒
        int year = now.getYear();
        int month = now.getMonthValue(); // 获取数字月份(1-12)
        int day = now.getDayOfMonth();
        int hour = now.getHour();
        int minute = now.getMinute();
        int second = now.getSecond();
        // 扩展字段:星期、本月天数、年内天数
        int week = now.getDayOfWeek().getValue(); // 星期(1=周一,7=周日)
        int dayOfYear = now.getDayOfYear();
        // 打印结果
        System.out.println("年:" + year + " 月:" + month + " 日:" + day);
        System.out.println("时:" + hour + " 分:" + minute + " 秒:" + second);
        System.out.println("本周星期:" + week + " 当年第" + dayOfYear + "天");
    }
}
输出结果:
年:2026 月:3 日:21
时:16 分:10 秒:25
本周星期:6 当年第80天
#### 1.2.3 日期时间运算:增减操作 `LocalDateTime`提供`plusXxx()`(增加)与`minusXxx()`(减少)方法,支持天、周、月、年、时、分、秒等维度运算。**所有方法均返回新对象**,原对象保持不变,因此务必接收返回值。
import java.time.LocalDateTime;
public class LocalDateTimeCalculate {
    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        System.out.println("原始时间:" + now);
        // 增加时间:1天、2周、3个月、1年、2小时
        LocalDateTime plusTime = now.plusDays(1).plusWeeks(2).plusMonths(3).plusYears(1).plusHours(2);
        System.out.println("增加后时间:" + plusTime);
        // 减少时间:3天、1周、2个月、10小时
        LocalDateTime minusTime = now.minusDays(3).minusWeeks(1).minusMonths(2).minusHours(10);
        System.out.println("减少后时间:" + minusTime);
    }
}
输出结果:
原始时间:2026-03-21T16:15:30.123
增加后时间:2027-06-05T18:15:30.123
减少后时间:2026-01-15T06:15:30.123
#### 1.2.4 特殊时间获取:借助TemporalAdjusters 利用`TemporalAdjusters`工具类配合`with()`方法,可快速获取**本周一/周日、本月首日/末日、本年首日/末日**等特殊时间点,无需手动推算。
import java.time.LocalDateTime;
import java.time.DayOfWeek;
import java.time.temporal.TemporalAdjusters;
public class LocalDateTimeAdjust {
    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        System.out.println("当前时间:" + now);
        // 1. 本周周一和周日(previousOrSame/nextOrSame:当前或最近的指定星期)
        LocalDateTime monday = now.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
        LocalDateTime sunday = now.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
        // 2. 本月第一天和最后一天
        LocalDateTime firstDayOfMonth = now.with(TemporalAdjusters.firstDayOfMonth());
        LocalDateTime lastDayOfMonth = now.with(TemporalAdjusters.lastDayOfMonth());
        // 3. 本年第一天和最后一天
        LocalDateTime firstDayOfYear = now.with(TemporalAdjusters.firstDayOfYear());
        LocalDateTime lastDayOfYear = now.with(TemporalAdjusters.lastDayOfYear());
        // 打印结果
        System.out.println("本周周一:" + monday);
        System.out.println("本周周日:" + sunday);
        System.out.println("本月第一天:" + firstDayOfMonth);
        System.out.println("本月最后一天:" + lastDayOfMonth);
        System.out.println("本年第一天:" + firstDayOfYear);
        System.out.println("本年最后一天:" + lastDayOfYear);
    }
}
输出结果:
当前时间:2026-03-21T16:20:00.123
本周周一:2026-03-17T16:20:00.123
本周周日:2026-03-23T16:20:00.123
本月第一天:2026-03-01T16:20:00.123
本月最后一天:2026-03-31T16:20:00.123
本年第一天:2026-01-01T16:20:00.123
本年最后一天:2026-12-31T16:20:00.123
## 二、BigDecimal:高精度数值运算救星 在涉及金额、税率、汇率等敏感计算时,`double`和`float`会因**二进制存储机制**产生精度丢失(如`0.1 + 0.2`的结果并非精确的`0.3`)。`BigDecimal`位于`java.math`包,是**不可变、线程安全**的高精度数值类,支持任意精度的小数运算。 ### 2.1 创建BigDecimal的正确姿势 ⚠️ `BigDecimal`提供多种构造方法,但**使用`double`构造存在精度丢失风险**。推荐使用**字符串构造方法**,或通过`valueOf()`静态方法(内部自动将`double`转为字符串)。
import java.math.BigDecimal;
public class BigDecimalCreate {
    public static void main(String[] args) {
        // 错误方式:double构造方法,精度丢失
        BigDecimal bigDecimalDouble = new BigDecimal(0.1);
        // 正确方式1:字符串构造方法,精确表示
        BigDecimal bigDecimalStr = new BigDecimal("0.1");
        // 正确方式2:valueOf静态方法,处理double类型
        BigDecimal bigDecimalValueOf = BigDecimal.valueOf(0.1);
        // 打印结果对比
        System.out.println("double构造方法:" + bigDecimalDouble);
        System.out.println("字符串构造方法:" + bigDecimalStr);
        System.out.println("valueOf静态方法:" + bigDecimalValueOf);
    }
}
输出结果:
double构造方法:0.1000000000000000055511151231257827021181583404541015625
字符串构造方法:0.1
valueOf静态方法:0.1
从输出可清晰看到,`new BigDecimal(0.1)`产生了精度误差,而字符串构造和`valueOf()`均得到精确值。 ### 2.2 加减乘基础运算 `BigDecimal`的加减乘通过`add()`、`subtract()`、`multiply()`方法实现。**所有方法均返回新对象**,运算时需保证操作数均为`BigDecimal`类型,且建议通过字符串构造创建。
import java.math.BigDecimal;
public class BigDecimalAddSubMul {
    public static void main(String[] args) {
        // 初始化两个高精度数值(金额:100.50 元和 25.30 元)
        BigDecimal num1 = new BigDecimal("100.50");
        BigDecimal num2 = new BigDecimal("25.30");
        // 加法:100.50 + 25.30
        BigDecimal addResult = num1.add(num2);
        // 减法:100.50 - 25.30
        BigDecimal subResult = num1.subtract(num2);
        // 乘法:100.50 * 25.30
        BigDecimal mulResult = num1.multiply(num2);
        // 打印结果
        System.out.println("加法结果:" + addResult + " 元");
        System.out.println("减法结果:" + subResult + " 元");
        System.out.println("乘法结果:" + mulResult + " 元");
    }
}
输出结果:
加法结果:125.80 元
减法结果:75.20 元
乘法结果:2542.6500 元
### 2.3 除法运算与舍入模式 除法运算相对特殊——若结果为无限循环小数,直接调用无参`divide()`会抛出`ArithmeticException`异常。因此实际开发中**必须指定精度和舍入模式**。 #### 8种舍入模式一览 `RoundingMode`枚举定义了8种舍入模式,其中最常用的是`HALF_UP`(四舍五入)。财务结算场景推荐`HALF_EVEN`(银行家舍入法),其他模式如下:
舍入模式核心规则示例
远离零舍入1.234→1.24,-1.234→-1.24
接近零舍入1.236→1.23,-1.236→-1.23
四舍五入1.235→1.24,-1.235→-1.24
银行家舍入(奇数进,偶数舍)1.235→1.24,1.245→1.24
断言精确结果,否则抛异常10/2=5(正常),1/3(抛异常)
#### 除法实战:指定精度 + 舍入模式 推荐使用三参形式:`divide(divisor, scale, roundingMode)`,其中`scale`为保留位数,`roundingMode`为舍入策略。
import java.math.BigDecimal;
import java.math.RoundingMode;
public class BigDecimalDivide {
    public static void main(String[] args) {
        // 初始化数值:10.0 除以 3.0(无限循环小数)
        BigDecimal num1 = new BigDecimal("10.0");
        BigDecimal num2 = new BigDecimal("3.0");
        // 方式1:指定精度(保留2位小数)+ 四舍五入模式(最常用)
        BigDecimal divide1 = num1.divide(num2, 2, RoundingMode.HALF_UP);
        // 方式2:指定精度(保留3位小数)+ 银行家舍入法
        BigDecimal divide2 = num1.divide(num2, 3, RoundingMode.HALF_EVEN);
        // 方式3:通过MathContext指定精度和模式(总精度4位,四舍五入)
        BigDecimal divide3 = num1.divide(num2, new java.math.MathContext(4, RoundingMode.HALF_UP));
        // 打印结果
        System.out.println("10.0/3.0(保留2位,四舍五入):" + divide1);
        System.out.println("10.0/3.0(保留3位,银行家舍入):" + divide2);
        System.out.println("10.0/3.0(总精度4位,四舍五入):" + divide3);
    }
}
输出结果:
10.0/3.0(保留2位,四舍五入):3.33
10.0/3.0(保留3位,银行家舍入):3.333
10.0/3.0(总精度4位,四舍五入):3.333
## 三、核心要点总结 掌握这两个工具类的正确用法,能有效避开开发中的常见陷阱,提升代码的健壮性和可维护性。无论你擅长Python、Java、C++、TypeScript还是Go,在处理日期与精度问题时,这些设计思想都值得借鉴。 | 场景 | 推荐方案 | 关键注意点 | |------|---------|-----------| | 日期时间处理 | `LocalDateTime` + `DateTimeFormatter` | 线程安全、无偏移、不可变 | | 高精度运算 | `BigDecimal` | 字符串构造、指定舍入模式 | | 共性特征 | 不可变类 | 方法返回新对象,原对象不变 | **核心要点:** - ✅ 日期处理优先使用`LocalDateTime`,摒弃过时的`Date`类 - ✅ `BigDecimal`必须通过字符串构造或`valueOf()`创建 - ✅ 除法运算务必指定精度与舍入模式,常用`HALF_UP` - ✅ 两者均为不可变类,天然线程安全,适合高并发场景 [AFFILIATE_SLOT_1] [AFFILIATE_SLOT_2] 掌握这些工具,你的Java代码将更加健壮、可靠。立即动手实践,告别精度丢失与日期偏移的困扰吧! UPDOWNHALF_UPHALF_EVENUNNECESSARY