jdk8中的LocalTime,LocalDate,LocalDateTime与jdk8之前的Date,SimpleDateFormat比较

Date类型和SimpleDateFormat的弊端

对于一个Java开发来说,DateUtil这个工具类,大家一定不陌生。每个项目中,都会封装这样一个工具类,在其中提供了当前时间获取,当前日期获取,以及时间或日期形式和字符串形式的各种格式的相互转换,用来解决日期形式繁杂的转换。然而就算是封装好了,每次用到日期形式转换的时候,都要点进工具类里去查看方法的实现,才能知道自己想要的是哪个。虽然麻烦,但是在单线程的应用中倒也没多少问题。
然而在多线程的应用中,日期的格式化则有可能出现问题。看下面的一个实例:

private static void simpleDateTime(){
        // 创建calendar类
        Calendar startDate = Calendar.getInstance();
        // 设置开始时间
        startDate.set(1990,01,01,1,0,0);
        List<Date> dateList = new ArrayList<>();
        // 以一个月为间隔创建200个时间
        for(int i = 0; i<200;i++){
            startDate.add(Calendar.MONTH,1);
            dateList.add(startDate.getTime());
        }
        // 创建线程公用的SimpleDateFormat
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        // 创建线程池,固定核心线程为2
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        // 创建线程(此处使用了lambda表达式)
        dateList.forEach(date ->{
            // 将线程加入线程池中
            executorService.submit(new Thread(()->{
                //转换日期格式
                String testDat = sdf.format(date);
                System.out.println("simpleDateTime:"+Thread.currentThread().getName()+":"+date +":"+testDat);
            }));
        });
        // 关闭线程池
        executorService.shutdown();
    }

输出结果如下(只截取了能说明问题的部分结果):
在这里插入图片描述
这两处的格式转换前和转换后不一致。出现这个问题的原因就是SimpleDateFormat的线程不安全性导致的。关于这个问题的说明,网上有很多可以自行查阅。

综上,Date以及Calendar类型操作复杂,用于处理时间格式的SimpleDateFormat又不能保证线程安全,这两点一直被Java开发者诟病。对于SimpleDateFormat的非线程安全问题,网上也有很多解决的方案,大致列举如下:

  • 在每个线程实例中都新建一个SimpleDateFormat实例。缺点:每次创建线程实例都要新建format对象,消耗内存,增加GC负担。
  • 给SimpleDateFormat对象加锁,使用Lock或者synchronized修饰。缺点:个线程线性执行,性能差。
  • 使用ThreadLocal为每个线程创建一个SimpleDateFormat对象副本。优点是有线程隔离性,各自的副本对象不会影响其他副本。

以上解决方案中,数方案三最优,但即便如此,也增加了代码的复杂度。

jdk8中的全新时间类型

在jdk8中,加入了全新的时间,日期类型。然而很多Java开发者都不知道或者说不了解这些新的类型。
在这里插入图片描述
看看官方文档如何描述LocalDateTime:

Implementation Requirements:

This class is immutable and thread-safe.
详细说明

意思就是说,此类是不可变的并且是线程安全的。并且支持的日期范围,我认为已经扩大到了人类文明用不完(开玩笑)。
在这里插入图片描述

当然最重要的有两点,这些时间类型
  1. 线程安全。
  2. 使用非常方便,简单,无需封装。
    是时候和DateUtil工具类说再见了。
为什么是线程安全的?

那么,LocalDateTime的不可变指的是什么呢?又是怎样保证线程安全性的?
上源码:

public final class LocalDateTime
        implements Temporal, TemporalAdjuster, ChronoLocalDateTime<LocalDate>, Serializable {

    /**
     * The minimum supported {@code LocalDateTime}, '-999999999-01-01T00:00:00'.
     * This is the local date-time of midnight at the start of the minimum date.
     * This combines {@link LocalDate#MIN} and {@link LocalTime#MIN}.
     * This could be used by an application as a "far past" date-time.
     */
    public static final LocalDateTime MIN = LocalDateTime.of(LocalDate.MIN, LocalTime.MIN);
    /**
     * The maximum supported {@code LocalDateTime}, '+999999999-12-31T23:59:59.999999999'.
     * This is the local date-time just before midnight at the end of the maximum date.
     * This combines {@link LocalDate#MAX} and {@link LocalTime#MAX}.
     * This could be used by an application as a "far future" date-time.
     */
    public static final LocalDateTime MAX = LocalDateTime.of(LocalDate.MAX, LocalTime.MAX);

    /**
     * Serialization version.
     */
    private static final long serialVersionUID = 6207766400415563566L;

    /**
     * The date part.
     */
    private final LocalDate date;
    /**
     * The time part.
     */
    private final LocalTime time;

LocalDate类是final类型的,也就是说,LocalDate是不可变的,一旦实例化,值就固定了。而java8之前的Date类不是final的。这就是此类不可变的含义所在。在LocalDateTime类的format方法中,也使用了格式化工具类DateTimeFormatter,不同于SimpleDateFormat的是,这个DateTimeFormatter也是不可变且线程安全的。

final关键字的内存语义
1,写final域的重排序规则:JMM禁止编译器把final域的写重排序到构造函数初始化之外(之后)。编译器会在final域的写之后,构造函数return之前,插入一个StoreStore内存屏障。
2,读final域的重排序规则:在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止重排序这2个操作。编译器会在读final域操作的前面插入一个LoadLoad屏障。
解读:读final域的重排序规则可以确保,在读一个对象的final域之前,一定会先读这个final域的对象引用。也就是说,可以确保final修饰的对象this.fieldfield是true。但是普通的非final修饰的对象,不能确保this.fieldfield是true。

以上内容引用自Java8提供的LocalDate和DateTimeFormat是如何保证线程安全的?

还是可以通过代码来验证是不是这样。代码如下:

private static void localDateTime(){
        LocalDateTime startTime = LocalDateTime.of(1990,01,01,1,0,0);
        List<LocalDateTime> dateList = new ArrayList<>();
        for(int i = 0; i<200;i++){
            dateList.add(startTime.plusMonths(i));
        }

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        dateList.forEach(date ->{

            executorService.submit(new Thread(()->{

                String testLdt = date.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
                System.out.println("localDateTime:"+Thread.currentThread().getName()+":"+date +":"+testLdt);
            }));

        });

        executorService.shutdown();
    }

输出结果是,200个转换的结果和原日期一致。这里就不截图了。

使用方法
  1. 按照自由格式获取当前时间
public static String getTodayByFormat(String timeFormat){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(timeFormat));
    }
  1. 获取当前年/月/日
    在这里插入图片描述

  2. 获取前一天

public static String getYesterdayByFormat(String timeFormat){
        return LocalDateTime.now().minusDays(1).format(DateTimeFormatter.ofPattern(timeFormat));
    }

同理,获取后一天用plusDays方法。其他plus和minus方法如下:
在这里插入图片描述
在这里插入图片描述
值得注意的是,所有方法的返回值都是LocalDateTime,也就意味着可以一直链式点下去。例如想要一年三个月12天后的数据,也能轻松得到。

  1. 获取指定时间的日期类型。简单
LocalDateTime startTime = LocalDateTime.of(1990,01,01,1,0,0);

和calendar一样。

// 根据字符串取:
LocalDate endOfFeb = LocalDate.parse("2014-02-28"); // 严格按照ISO yyyy-MM-dd验证,02写成2都不行,当然也有一个重载方法允许自己定义格式
LocalDate.parse("2014-02-29"); // 无效日期无法通过:DateTimeParseException: Invalid date
  1. 其他特殊要求的日期获取
// 取本月第1天:
LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); // 2019-10-01
// 取本月第2天:
LocalDate secondDayOfThisMonth = today.withDayOfMonth(2); // 2019-10-02
// 取本月最后一天,再也不用计算是28,29,30还是31:
LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); // 2019-10-31
// 取下个月第一天:
LocalDate firstDayOf2019 = lastDayOfThisMonth.plusDays(1); // 变成了2019-11-01
// 取2019年1月第一个周一,这个计算用Calendar要死掉很多脑细胞:
LocalDate firstMondayOf2019 = LocalDate.parse("2019-01-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)); // 2015-01-05

注意:TemporalAdjusters是日期形式调整工具类。详情参见TemporalAccessor官方文档

以上就是对Date和LocalDateTime的对比。其中LocalDate和LocalTime没有做介绍,因为他们和LocalDateTime一样,就不赘述。希望能给到大家一些帮助。
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是关于JDK 8日期相关类的介绍和示例: 1. LocalDate类: - LocalDate类表示一个不可变的日期对象,它只包含日期部分(年、月、日)。 - 使用`now()`方法获取当前日期。 - 使用`of()`方法创建指定日期。 - 使用`getXXX()`方法获取日期的年、月、日等部分。 - 使用`plusXXX()`和`minusXXX()`方法进行日期的加减操作。 - 使用`isXXX()`方法判断日期的属性,如是否为闰年等。 示例代码: ```java import java.time.LocalDate; // 获取当前日期 LocalDate currentDate = LocalDate.now(); System.out.println("当前日期: " + currentDate); // 创建指定日期 LocalDate specificDate = LocalDate.of(2022, 1, 1); System.out.println("指定日期: " + specificDate); // 获取日期的年、月、日 int year = currentDate.getYear(); int month = currentDate.getMonthValue(); int day = currentDate.getDayOfMonth(); System.out.println("年: " + year + ", 月: " + month + ", 日: " + day); // 日期的加减操作 LocalDate futureDate = currentDate.plusDays(7); LocalDate pastDate = currentDate.minusMonths(1); System.out.println("未来日期: " + futureDate); System.out.println("过去日期: " + pastDate); // 判断是否为闰年 boolean isLeapYear = currentDate.isLeapYear(); System.out.println("是否为闰年: " + isLeapYear); ``` 2. LocalTime类: - LocalTime类表示一个不可变的时间对象,它只包含时间部分(时、分、秒、纳秒)。 - 使用`now()`方法获取当前时间。 - 使用`of()`方法创建指定时间。 - 使用`getXXX()`方法获取时间的时、分、秒等部分。 - 使用`plusXXX()`和`minusXXX()`方法进行时间的加减操作。 示例代码: ```java import java.time.LocalTime; // 获取当前时间 LocalTime currentTime = LocalTime.now(); System.out.println("当前时间: " + currentTime); // 创建指定时间 LocalTime specificTime = LocalTime.of(12, 30, 0); System.out.println("指定时间: " + specificTime); // 获取时间的时、分、秒 int hour = currentTime.getHour(); int minute = currentTime.getMinute(); int second = currentTime.getSecond(); System.out.println("时: " + hour + ", 分: " + minute + ", 秒: " + second); // 时间的加减操作 LocalTime futureTime = currentTime.plusHours(2); LocalTime pastTime = currentTime.minusMinutes(30); System.out.println("未来时间: " + futureTime); System.out.println("过去时间: " + pastTime); ``` 3. LocalDateTime类: - LocalDateTime类表示一个不可变的日期时间对象,它包含日期和时间部分。 - 使用`now()`方法获取当前日期时间。 - 使用`of()`方法创建指定日期时间。 - 使用`getXXX()`方法获取日期时间的年、月、日、时、分、秒等部分。 - 使用`plusXXX()`和`minusXXX()`方法进行日期时间的加减操作。 示例代码: ```java import java.time.LocalDateTime; // 获取当前日期时间 LocalDateTime currentDateTime = LocalDateTime.now(); System.out.println("当前日期时间: " + currentDateTime); // 创建指定日期时间 LocalDateTime specificDateTime = LocalDateTime.of(2022, 1, 1, 12, 30, 0); System.out.println("指定日期时间: " + specificDateTime); // 获取日期时间的年、月、日、时、分、秒 int year = currentDateTime.getYear(); int month = currentDateTime.getMonthValue(); int day = currentDateTime.getDayOfMonth(); int hour = currentDateTime.getHour(); int minute = currentDateTime.getMinute(); int second = currentDateTime.getSecond(); System.out.println("年: " + year + ", 月: " + month + ", 日: " + day); System.out.println("时: " + hour + ", 分: " + minute + ", 秒: " + second); // 日期时间的加减操作 LocalDateTime futureDateTime = currentDateTime.plusDays(7); LocalDateTime pastDateTime = currentDateTime.minusMonths(1); System.out.println("未来日期时间: " + futureDateTime); System.out.println("过去日期时间: " + pastDateTime); ``` 4. Calendar类: - Calendar类是Java旧版的日期时间处理类,JDK 8之后推荐使用新的日期时间API。 - Calendar类可以用于获取和设置日期时间的各个部分,如年、月、日、时、分、秒等。 - 使用`getInstance()`方法获取当前日期时间的Calendar实例。 - 使用`get()`方法获取日期时间的各个部分。 - 使用`set()`方法设置日期时间的各个部分。 示例代码: ```java import java.util.Calendar; // 获取当前日期时间的Calendar实例 Calendar currentCalendar = Calendar.getInstance(); System.out.println("当前日期时间: " + currentCalendar.getTime()); // 获取日期时间的年、月、日、时、分、秒 int year = currentCalendar.get(Calendar.YEAR); int month = currentCalendar.get(Calendar.MONTH) + 1; // 月份从0开始,需要加1 int day = currentCalendar.get(Calendar.DAY_OF_MONTH); int hour = currentCalendar.get(Calendar.HOUR_OF_DAY); int minute = currentCalendar.get(Calendar.MINUTE); int second = currentCalendar.get(Calendar.SECOND); System.out.println("年: " + year + ", 月: " + month + ", 日: " + day); System.out.println("时: " + hour + ", 分: " + minute + ", 秒: " + second); // 设置日期时间的年、月、日、时、分、秒 currentCalendar.set(Calendar.YEAR, 2022); currentCalendar.set(Calendar.MONTH, 0); // 月份从0开始,0表示1月 currentCalendar.set(Calendar.DAY_OF_MONTH, 1); currentCalendar.set(Calendar.HOUR_OF_DAY, 12); currentCalendar.set(Calendar.MINUTE, 30); currentCalendar.set(Calendar.SECOND, 0); System.out.println("设置后的日期时间: " + currentCalendar.getTime()); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值