【JDK8新特性】7.新日期API

本文介绍了Java8中新的日期API的优点及使用方法,对比了旧版日期API存在的多线程安全问题,并通过示例展示了如何利用新API进行日期处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

新日期API

基于尚硅谷java8教程

1. 原有日期api的缺点

  • 从jdk1.1开始创建,日期处理没有规范,处于多个包中比如:java.util.Date,java.text.java.text.DateFormat等
  • 现有的日期api存在多线程的线程安全问题(当然可以通过比如ThreadLocal等方式规避)
/*存在线程安全问题的旧版本日期api*/
 /**
     * 存在多线程安全问题
     * 异常信息如下:
     * java.util.concurrent.ExecutionException: java.lang.NumberFormatException: multiple points
     at java.util.concurrent.FutureTask.report(FutureTask.java:122)
     at java.util.concurrent.FutureTask.get(FutureTask.java:192)
     at com.seven.jdk8.SimpleDateFormatTest.test(SimpleDateFormatTest.java:30)
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
     at java.lang.reflect.Method.invoke(Method.java:497)
     at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
     at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
     at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
     at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
     at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
     at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
     at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
     at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
     at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
     at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
     at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
     at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
     at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
     at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
     at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:119)
     at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
     at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
     at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
     at java.lang.reflect.Method.invoke(Method.java:497)
     at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
     Caused by: java.lang.NumberFormatException: multiple points
     at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
     at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
     at java.lang.Double.parseDouble(Double.java:538)
     at java.text.DigitList.getDouble(DigitList.java:169)
     at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
     at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1867)
     at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
     at java.text.DateFormat.parse(DateFormat.java:364)
     at com.seven.jdk8.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:19)
     at com.seven.jdk8.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:16)
     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
     at java.lang.Thread.run(Thread.java:745)
     */
    @Test
    public void test(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
		Callable<Date> task = new Callable<Date>() {
			@Override
			public Date call() throws Exception {
				return sdf.parse("20161121");
			}
		};
		ExecutorService pool = Executors.newFixedThreadPool(10);
		List<Future<Date>> results = new ArrayList<>();
		for (int i = 0; i < 10; i++) {
			results.add(pool.submit(task));
		}

        try {
            for(Future<Date> ft:results){
                System.out.println(ft.get());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        pool.shutdown();
    }

/*对于现有的日期api如何规避线程安全问题*/
 /*使用ThreadLocal解决线程安全问题*/
    @Test
    public void test2(){
        Callable<Date> task = new Callable<Date>() {
            @Override
            public Date call() throws Exception {
                return DateFormatThreadLocal.parse("20151125","yyyyMMdd");
            }
        };
        ExecutorService pool = Executors.newFixedThreadPool(10);
        List<Future<Date>> results = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            results.add(pool.submit(task));
        }

        try {
            for(Future<Date> ft:results){
                System.out.println(ft.get());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        pool.shutdown();
    }
 
// 解决这个问题依赖的工具类
package com.seven.jdk8;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 *使用ThreadLocal的方式解决多线程问题
 * .
 */
public class DateFormatThreadLocal {
    /**
     * 锁对象
     */
    private static final Object lockObj = new Object();

    /**
     * 存放不同的日期模板格式的sdf的Map
     */
    private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<String, ThreadLocal<SimpleDateFormat>>();

    /**
     * 返回一个ThreadLocal的sdf,每个线程只会new一次sdf
     *
     * @param pattern
     * @return
     */
    private static SimpleDateFormat getSdf(final String pattern) {
        ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattern);

        // 此处的双重判断和同步是为了防止sdfMap这个单例被多次put重复的sdf
        if (tl == null) {
            synchronized (lockObj) {
                tl = sdfMap.get(pattern);
                if (tl == null) {
                    // 只有Map中还没有这个pattern的sdf才会生成新的sdf并放入map
                    System.out.println("put new sdf of pattern " + pattern + " to map");

                    // 这里是关键,使用ThreadLocal<SimpleDateFormat>替代原来直接new SimpleDateFormat
                    tl = new ThreadLocal<SimpleDateFormat>() {

                        @Override
                        protected SimpleDateFormat initialValue() {
                            System.out.println("thread: " + Thread.currentThread() + " init pattern: " + pattern);
                            return new SimpleDateFormat(pattern);
                        }
                    };
                    sdfMap.put(pattern, tl);
                }
            }
        }

        return tl.get();
    }
    /**
     * 是用ThreadLocal<SimpleDateFormat>来获取SimpleDateFormat,这样每个线程只会有一个SimpleDateFormat
     *
     * @param date
     * @param pattern
     * @return
     */
    public static String format(Date date, String pattern) {
        return getSdf(pattern).format(date);
    }

    public static Date parse(String dateStr, String pattern) throws ParseException {
        return getSdf(pattern).parse(dateStr);
    }
}

2. 新日期api简介

  • 优势

    • 新日期api是线程安全的
    • 统一放在java.time及其子包中
    • 关注点分离,对于机器使用的时间戳和人可读的日期进行了类的分类
  • java.time及其子包说明

    • java.time包:这是新的Java日期/时间API的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。所有这些类都是不可变的和线程安全的,在绝大多数情况下,这些类能够有效地处理一些公共的需求。
    • java.time.chrono包:这个包为非ISO的日历系统定义了一些泛化的API,我们可以扩展AbstractChronology类来创建自己的日历系统。
    • java.time.format包:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为java.time包中相应的类已经提供了格式化和解析的方法。
    • java.time.temporal包:这个包包含一些时态对象,我们可以用其找出关于日期/时间对象的某个特定日期或时间,比如说,可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法,因为它们都具有“withXXX”的格式。
    • java.time.zone包:这个包包含支持不同时区以及相关规则的类。

3. 使用范例

    
package com.seven.jdk8;

import org.junit.Test;

import java.text.DateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.TemporalField;
import java.util.Date;
import java.util.Set;

/**
 * 新日期api使用测试.
 */
public class LocalDateTimeTest {
    /* localDate/localtime/localdateTime */
    @Test
    public void test1(){
        //获取当前日期时间
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(ldt);
        //按照指定时间生成日期
        LocalDateTime ldt1 = LocalDateTime.of(2020,12,30,1,2,3);
        System.out.println(ldt1);
        //指定时间+2年
        System.out.println(ldt.plusYears(2));
        //指定时间-3月
        LocalDateTime ldt2 = ldt.minusMonths(3);
        System.out.println(ldt2);
        System.out.println(ldt.getYear());//获取年份

        System.out.println(LocalDate.now());//获取日期
    }

    /* 时间戳 (使用Unix元年 1970年1月1日 00:00:00到现在的毫秒数 ) */
    @Test
    public void test2(){
        Instant ins = Instant.now();  //默认使用UTC时区
        System.out.println(ins+",,,"+ins.getEpochSecond());

        OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));//中国在东八区
        System.out.println(odt);

        Instant ins1 = Instant.ofEpochSecond(5);
        System.out.println(ins1);  //1970-01-01T00:00:05Z 从Unix元年偏移5s
    }

    /**
     * Duration : 用于计算两个“时间”间隔
     * Period : 用于计算两个“日期”间隔
    * */
    @Test
    public void test3() throws InterruptedException {
        Instant ins1 = Instant.now();
        Thread.sleep(1000);
        Instant ins2 = Instant.now();
        System.out.println("两个时间的间隔为--》"+Duration.between(ins1,ins2)); //两个时间的间隔为--》PT1S

        LocalDate date1 = LocalDate.of(2011,3,5);
        LocalDate date2 = LocalDate.now();
        Period pe = Period.between(date1,date2);
        System.out.println("两个日期的间隔为--》"+ pe+",,间隔的年为--》"+pe.getYears()); //两个日期的间隔为--》P6Y18D,,间隔的年为--》6
    }

    /* 时间校正器   TemporalAdjuster       */
    @Test
    public void test4(){
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println("今天几号--> "+ldt.getDayOfMonth()); //今天几号--> 23
        System.out.println("下个星期天是几号--》"+ ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY))); //下个星期天是几号--》2017-03-26T18:36:30.477

        // 自定义下一个工作日
        LocalDateTime dateTime = ldt.with((temporal)->{
            LocalDateTime lt = (LocalDateTime)temporal;
            DayOfWeek dow = lt.getDayOfWeek();
            if(DayOfWeek.FRIDAY.equals(dow)){
                return lt.plusDays(3);
            }else if(DayOfWeek.SATURDAY.equals(dow)){
                return lt.plusDays(2);
            }else{
                return lt.plusDays(1);
            }
        });
        System.out.println("下个工作日是--》"+dateTime);  //下个工作日是--》2017-03-24T22:42:31.789
    }

    /* 格式化日期 */
    @Test
    public void test5(){
        // 自定义格式,当然也可以使用默认指定的格式
        DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(format.format(ldt));  //2017-03-23 22:49:37

        //字符串转日期
        LocalDateTime ldt2 = LocalDateTime.parse("2017-11-12 23:10:05",format);
        System.out.println(ldt2); //2017-11-12T23:10:05
    }

    /* 带时区的日期  ZonedDate、ZonedTime、ZonedDateTime */
    @Test
    public void test6(){
        LocalDateTime ldt1 = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); //Asia/shanghai time ->2017-03-23T22:57:21.084
        System.out.println("Asia/shanghai time ->"+ldt1);

        ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/Marigot"));
        System.out.println(zdt); //2017-03-23T10:59:43.708-04:00[America/Marigot]
        System.out.println(zdt.toLocalDateTime());//2017-03-23T11:00:49.177 转换为当前时区时间

        System.out.println("------------------------------------------------");
        //获取时区ID列表
        //ZoneId.getAvailableZoneIds().stream().forEach(System.out::println);
    }
}

源代码地址:http://git.oschina.net/johnny/java_base

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值