16.Java常用工具类精讲:日期处理与精确运算
2026/7/13 15:51:48 网站建设 项目流程

目录

写在前面

第一部分:日期时间处理

一、从Date到LocalDateTime:一次必要的进化

1.1 曾经的王者:Date类

1.2 新时代的选择:LocalDateTime

二、LocalDateTime的常用操作

2.1 获取日期时间的各个字段

2.2 获取更详细的时间信息

2.3 日期时间的加减运算

2.4 获取当前日期所在周的周一和周日

2.5 获取当前日期所在月的第一天和最后一天

第二部分:BigDecimal与精确运算

三、为什么需要BigDecimal?

四、BigDecimal的创建

4.1 构造方法

4.2 一个值得警惕的问题

五、BigDecimal的常用运算

5.1 加减乘运算

5.2 除法运算与精度控制

5.3 舍入模式详解

5.4 使用MathContext指定精度

六、实战练习

总结


写在前面

在Java编程的日常开发中,有两类需求几乎每个项目都会遇到:一是对日期和时间的各种操作,二是对数值进行精确计算。前者关系到日志记录、定时任务、数据统计等功能,后者则直接影响到金融交易、科学计算等对精度要求极高的场景。

Java在这两方面都提供了丰富的工具类。早期的Date类虽然功能完备,但设计上存在不少瑕疵,很多方法已被标记为过时。好在Java 8引入了全新的日期时间API,彻底改变了我们处理日期的方式。而在数值计算方面,BigDecimal类则为高精度运算提供了可靠的保障。

今天这篇文章,我们就来系统地学习这两类工具类的使用方法和最佳实践。


第一部分:日期时间处理

一、从Date到LocalDateTime:一次必要的进化

1.1 曾经的王者:Date类

在Java早期版本中,java.util.Date是处理日期和时间的核心类。它表示一个特定的时间点,精确到毫秒。

import java.util.Date; public class DateDemo { public static void main(String[] args) { // 获取当前时间 Date now = new Date(); System.out.println(now); // 输出示例:Wed Oct 09 15:30:46 CST 2024 // 创建指定日期 Date specificDate = new Date(123, 2, 5); System.out.println(specificDate); // 输出:Sun Mar 05 00:00:00 CST 2023 } }

这里有几个需要注意的地方:

  • 年份偏移:参数123代表的是"1900 + 123 = 2023年",而不是直接表示2023年

  • 月份从0开始:2代表3月,0代表1月,以此类推

  • 未指定时间:如果没有指定时分秒,默认是00:00:00

这样的设计显然不够直观,也容易引发错误。翻阅官方文档会发现,Date类中大部分方法都被标记为@Deprecated(已过时)。这也是为什么我们现在更推荐使用Java 8引入的新日期API。

1.2 新时代的选择:LocalDateTime

LocalDateTime是Java 8中java.time包下的核心类。它解决了Date类的诸多痛点,设计更加清晰、易用。

创建LocalDateTime对象

import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class LocalDateTimeDemo { public static void main(String[] args) { // 方式一:获取当前日期和时间 LocalDateTime now = LocalDateTime.now(); System.out.println("当前时间:" + now); // 输出:2024-10-15T14:17:06.747 // 方式二:指定具体日期和时间 LocalDateTime specified = LocalDateTime.of(2023, 5, 15, 10, 30); System.out.println("指定时间:" + specified); // 输出:2023-05-15T10:30 // 方式三:从字符串解析 String dateStr = "2026-10-01 10:30:21"; DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime parsed = LocalDateTime.parse(dateStr, formatter); System.out.println("解析结果:" + parsed); // 输出:2026-10-01T10:30:21 } }

与Date相比,LocalDateTime的设计明显更加友好:年份直接写、月份从1开始、时间可以精确指定。格式化时使用DateTimeFormatter,模式字符串中的yyyyMMdd等含义清晰,不需要记忆奇怪的偏移量。


二、LocalDateTime的常用操作

2.1 获取日期时间的各个字段
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(); // 秒 System.out.println("年:" + year); System.out.println("月:" + month); System.out.println("日:" + day); System.out.println("时:" + hour); System.out.println("分:" + minute); System.out.println("秒:" + second);
2.2 获取更详细的时间信息
LocalDateTime now = LocalDateTime.now(); System.out.println("本周周几:" + now.getDayOfWeek().getValue()); // 周一=1,周二=2,...,周日=7 System.out.println("本月第几天:" + now.getDayOfMonth()); System.out.println("当年第几天:" + now.getDayOfYear());
2.3 日期时间的加减运算

LocalDateTime提供了丰富的plusminus方法,支持对年、月、周、日、时、分、秒进行增减操作:

LocalDateTime now = LocalDateTime.now(); // 增加1天 LocalDateTime plusOneDay = now.plusDays(1); System.out.println("增加1天:" + plusOneDay); // 减少1天 LocalDateTime minusOneDay = now.minusDays(1); System.out.println("减少1天:" + minusOneDay); // 增加1周 LocalDateTime plusOneWeek = now.plusWeeks(1); System.out.println("增加1周:" + plusOneWeek); // 增加1个月 LocalDateTime plusOneMonth = now.plusMonths(1); System.out.println("增加1月:" + plusOneMonth); // 增加1年 LocalDateTime plusOneYear = now.plusYears(1); System.out.println("增加1年:" + plusOneYear);

重要提醒:这些方法不会修改原始的LocalDateTime对象,而是返回一个新的对象。这是因为LocalDateTime被设计为不可变类,一旦创建就不能修改。这一点和String的设计思路是一致的。

2.4 获取当前日期所在周的周一和周日

在实际业务中,经常需要获取"本周一"或"本周日"的日期。TemporalAdjusters工具类提供了便捷的方法:

import java.time.DayOfWeek; import java.time.LocalDateTime; import java.time.temporal.TemporalAdjusters; LocalDateTime currentDate = LocalDateTime.now(); // 获取当前日期所在的当周周一 LocalDateTime firstDayOfWeek = currentDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)); // 获取当前日期所在的当周周日 LocalDateTime lastDayOfWeek = currentDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)); System.out.println("本周周一:" + firstDayOfWeek); System.out.println("本周周日:" + lastDayOfWeek);

previousOrSame的含义是:寻找当前日期或之前最近的指定星期几。如果今天是周三,调用previousOrSame(MONDAY)会返回本周一。

nextOrSame的含义是:寻找当前日期或之后最近的指定星期几。如果今天是周三,调用nextOrSame(SUNDAY)会返回本周日。

2.5 获取当前日期所在月的第一天和最后一天
LocalDateTime currentDate = LocalDateTime.now(); // 当月第一天 LocalDateTime firstDayOfMonth = currentDate.with(TemporalAdjusters.firstDayOfMonth()); // 当月最后一天 LocalDateTime lastDayOfMonth = currentDate.with(TemporalAdjusters.lastDayOfMonth()); System.out.println("本月第一天:" + firstDayOfMonth); System.out.println("本月最后一天:" + lastDayOfMonth);

第二部分:BigDecimal与精确运算

三、为什么需要BigDecimal?

在日常编程中,我们经常会使用doublefloat来处理小数。但你有没有遇到过这样的情况:

double a = 0.1; double b = 0.2; System.out.println(a + b); // 期望:0.3,实际:0.30000000000000004

这并不是Java的bug,而是二进制浮点数本身的局限性。计算机用二进制表示小数时,有些十进制小数无法被精确表示(就像十进制无法精确表示1/3一样)。这种精度误差在金融、科学计算等场景中是不能接受的。

BigDecimal就是为了解决这个问题而生的。它提供了任意精度的十进制运算能力。

四、BigDecimal的创建

4.1 构造方法

BigDecimal提供了多种构造方法:

import java.math.BigDecimal; BigDecimal fromInt = new BigDecimal(10); // 从整数创建 BigDecimal fromLong = new BigDecimal(100L); // 从长整数创建 BigDecimal fromDouble = new BigDecimal(0.1); // 从double创建(不推荐) BigDecimal fromString = new BigDecimal("0.1"); // 从字符串创建(推荐)
4.2 一个值得警惕的问题

来看这段代码:

BigDecimal d1 = new BigDecimal(1.99); BigDecimal d2 = new BigDecimal("1.99"); System.out.println(d1); // 输出:1.9899999999999999911182158029987476766109466552734375 System.out.println(d2); // 输出:1.99

同样的数值1.99,使用double构造器和字符串构造器得到了截然不同的结果。原因在于:new BigDecimal(1.99)接收的是一个已经存在精度损失的double值,BigDecimal只是忠实地还原了这个不精确的值。

官方文档明确指出:推荐优先使用字符串构造器。如果必须使用double值,可以考虑BigDecimal.valueOf(double)方法,它内部会先将double转为字符串再构造。

五、BigDecimal的常用运算

5.1 加减乘运算
BigDecimal a = new BigDecimal("1.35"); BigDecimal b = new BigDecimal("3.22"); // 加法 BigDecimal sum = a.add(b); System.out.println("加法:" + sum); // 4.57 // 减法 BigDecimal diff = a.subtract(b); System.out.println("减法:" + diff); // -1.87 // 乘法 BigDecimal product = a.multiply(b); System.out.println("乘法:" + product); // 4.3470

注意:BigDecimal对象是不可变的,加减乘运算都会返回新的BigDecimal对象,不会修改原对象。

5.2 除法运算与精度控制

除法运算比加减乘要复杂一些,因为两个数相除的结果可能是无限循环小数。

BigDecimal a = new BigDecimal("1.35"); BigDecimal b = new BigDecimal("3.22"); // 直接除法:如果结果是无限循环小数,会抛出异常 // BigDecimal result = a.divide(b); // ArithmeticException! // 正确做法:指定精度和舍入模式 BigDecimal result = a.divide(b, 4, RoundingMode.HALF_UP); System.out.println(result); // 0.4193

divide方法的三个参数分别是:

  • 精度:保留几位小数

  • 舍入模式:如何处理多余的小数位

5.3 舍入模式详解

BigDecimal提供了多种舍入模式,适用于不同的业务场景:

模式

说明

示例

UP

远离零方向舍入

1.5→2,-1.5→-2

DOWN

靠近零方向舍入

1.5→1,-1.5→-1

CEILING

向正无穷大方向舍入

1.5→2,-1.5→-1

FLOOR

向负无穷大方向舍入

1.5→1,-1.5→-2

HALF_UP

四舍五入(>=0.5向上)

1.5→2,1.4→1

HALF_DOWN

五舍六入(>0.5向上)

1.5→1,1.6→2

HALF_EVEN

银行家舍入法

1.5→2,2.5→2

UNNECESSARY

不允许舍入,否则抛异常

实际开发建议:绝大多数场景使用HALF_UP(四舍五入)即可。金融场景中有时会使用HALF_EVEN(银行家舍入法),因为它能最大程度减少舍入操作带来的统计偏差。

5.4 使用MathContext指定精度

除了直接在divide方法中指定精度,也可以使用MathContext对象:

BigDecimal a = new BigDecimal("1.35"); BigDecimal b = new BigDecimal("3.22"); MathContext mc = new MathContext(4, RoundingMode.HALF_UP); BigDecimal result = a.divide(b, mc); System.out.println(result); // 0.4193

MathContext的第一个参数是精度位数,第二个参数是舍入模式。如果不指定舍入模式,默认使用HALF_UP


六、实战练习

题目:​ 计算商品的总价,并对结果进行四舍五入保留两位小数。

import java.math.BigDecimal; import java.math.RoundingMode; public class ShoppingCart { public static void main(String[] args) { // 商品单价 BigDecimal price = new BigDecimal("29.99"); // 购买数量 BigDecimal quantity = new BigDecimal("3"); // 计算总价 BigDecimal total = price.multiply(quantity); System.out.println("原始总价:" + total); // 89.97 // 折扣 BigDecimal discountRate = new BigDecimal("0.88"); BigDecimal discountedTotal = total.multiply(discountRate); System.out.println("打折后总价:" + discountedTotal); // 79.1736 // 四舍五入保留两位小数 BigDecimal finalPrice = discountedTotal.setScale(2, RoundingMode.HALF_UP); System.out.println("最终支付金额:" + finalPrice); // 79.17 } }

总结

今天我们学习了Java中两个非常实用的工具类体系:

日期时间处理方面:

  • Date类是早期的日期时间类,存在设计缺陷,很多方法已过时

  • LocalDateTime是Java 8引入的新API,设计更清晰、使用更方便

  • 支持获取年、月、日、时、分、秒等各个字段

  • 支持对日期时间进行加减运算,返回新的不可变对象

  • 通过TemporalAdjusters可以方便地获取周初、月末等特殊日期

精确数值计算方面:

  • doublefloat存在精度问题,不适合金融等敏感场景

  • BigDecimal提供了任意精度的十进制运算能力

  • 创建BigDecimal时推荐使用字符串构造器,避免精度损失

  • 除法运算时必须指定精度和舍入模式

  • 舍入模式有多种选择,HALF_UP是最常用的四舍五入模式

掌握了这些工具类的使用,你在处理日期时间和数值计算时就能游刃有余了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询