Java中的时间和日期(四):与java8时间API有关的一些总结和补充

时间:2022-07-23
本文章向大家介绍Java中的时间和日期(四):与java8时间API有关的一些总结和补充,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

在了解完java8中新版本的时间API之后,当然,并不是全部了解,java.time包下面接近上百个类,没办法一一去了解。作为我们日常用来替换java.util.date的功能。也不需要全部了解。在看过若干代码之后,有如下总结。

1.关于Immutable对象的线程安全问题

如果在面试过程中,关于Immutable首先需要聊到的内容就是String类。String类内部是一个final修饰的字符数组。

 private final char value[];

一旦创建之后,就不能对这个对象做任何修改。也不会提供任何有关的set方法。如subString等方法都是产生一个新的对象。这样来保障了线程的安全性。 不可变对象的好处就是简单,然后可以很容易的复用。但是缺点是不得不为每次操作生成一个新的对象。如果不是太大的对象,在现有GC的能力之下,一般不会有太大的问题。当然,对于频繁的改变处理,String也提供了StringBuilder、StringBuffer等专门来处理这种频繁修改操作的工具。 在Effective java这本经典的著作之中第十七条:使可变性最小化–要求每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期( lifetime )内固定不变。 实现类的不可变性要遵守如下五条规则:

  • 1.不要提供任何会修改对象状态的方法(set方法)。
  • 2.保证类不会被扩展。这样可以防止粗心或者恶意的子类假装对象的状态已经改变,从而破坏该类的不可变行为。
  • 3.声明所有的域都是final的。通过系统的强制方式可以清楚地表明你的意图。
  • 4.声明所有的域都为私有的。这样可以防止客户端获得访问被域引用的可变对象的权限,井防止客户端直接修改这些对象。
  • 5.确保对子任何可变组件的互斥访问。如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用。并且,永远不要用客户端提供的对象引用来初始化这样的域,也不要从任何访问方法( accessor )中返回该对象引用。在构造器、访问方 法和readObject方法(详见第88条)中请使用保护性拷贝( defensive copy )技术(详见第50 条)。

我们可以查看所有新版本时间API相关的类。基本上全部的属性都是private final 修饰的。而且不提供任何set方法。 如在 Instant中:

    /**
     * Constant for the 1970-01-01T00:00:00Z epoch instant.
     */
    public static final Instant EPOCH = new Instant(0, 0);
    /**
     * The minimum supported epoch second.
     */
    private static final long MIN_SECOND = -31557014167219200L;
    /**
     * The maximum supported epoch second.
     */
    private static final long MAX_SECOND = 31556889864403199L;
    /**
     * The minimum supported {@code Instant}, '-1000000000-01-01T00:00Z'.
     * This could be used by an application as a "far past" instant.
     * <p>
     * This is one year earlier than the minimum {@code LocalDateTime}.
     * This provides sufficient values to handle the range of {@code ZoneOffset}
     * which affect the instant in addition to the local date-time.
     * The value is also chosen such that the value of the year fits in
     * an {@code int}.
     */
    public static final Instant MIN = Instant.ofEpochSecond(MIN_SECOND, 0);
    /**
     * The maximum supported {@code Instant}, '1000000000-12-31T23:59:59.999999999Z'.
     * This could be used by an application as a "far future" instant.
     * <p>
     * This is one year later than the maximum {@code LocalDateTime}.
     * This provides sufficient values to handle the range of {@code ZoneOffset}
     * which affect the instant in addition to the local date-time.
     * The value is also chosen such that the value of the year fits in
     * an {@code int}.
     */
    public static final Instant MAX = Instant.ofEpochSecond(MAX_SECOND, 999_999_999);

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

    /**
     * The number of seconds from the epoch of 1970-01-01T00:00:00Z.
     */
    private final long seconds;
    /**
     * The number of nanoseconds, later along the time-line, from the seconds field.
     * This is always positive, and never exceeds 999,999,999.
     */
    private final int nanos;

除关键的seconds和nanos之外,其他都是常量。而这两个值除了第一次赋值之后也不能修改。 另外我们再看看Instant的声明:

public final class Instant
        implements Temporal, TemporalAdjuster, Comparable<Instant>, Serializable {

final修饰的类,不得继承,这也很好的符合了不可变类定义中的第二条。之后没有提供对任何属性的set方法。 其他的方法主要有两类,分别是of和with开头的获取返回结果为Instant的方法和get某个属性值的方法。 而对u有of和with方法。都是在使用的时候通过new的方式创建了一个新的Instant对象。

  public static Instant ofEpochSecond(long epochSecond) {
        return create(epochSecond, 0);
    }

   private static Instant create(long seconds, int nanoOfSecond) {
        if ((seconds | nanoOfSecond) == 0) {
            return EPOCH;
        }
        if (seconds < MIN_SECOND || seconds > MAX_SECOND) {
            throw new DateTimeException("Instant exceeds minimum or maximum instant");
        }
        return new Instant(seconds, nanoOfSecond);
    }

这个create方法也是私有的,of以静态方法的形式提供给对外使用。 这样就很好的通过不可变类的形式,实现了线程安全。这也是我们自己在写代码的过程中值得借鉴的地方。

2.java8新版本时间如何存储到mysql

我们首先需要对mysql所支持的时间类型进行梳理:

日期时间类型

占用空间

日期格式

最小值

最大值

零值表示

DATETIME

8 bytes

YYYY-MM-DD HH:MM:SS

1000-01-01 00:00:00

9999-12-31 23:59:59

0000-00-00 00:00:00

TIMESTAMP

4 bytes

YYYY-MM-DD HH:MM:SS

19700101080001

2038-1-19 11:14:07

00000000000000

DATE

4 bytes

YYYY-MM-DD

1000-01-01

9999-12-31

0000-00-00

TIME

3 bytes

HH:MM:SS

-838:59:59

838:59:59

00:00:00

YEAR

1 bytes

YYYY

1901

2155

0000

上述是mysql4.2以上版本都支持的5种时间类型。 我们可以看到,基本能和java新版本的LocalDate、LocatTime、LocalDateTime都能对应得上。 需要注意的是,我们系统种的LocalDate、localDateTime、LocalTime都是采用的系统本地时区。如果使用这三个字段存入mysql的时候需要考虑数据库与业务系统时区一致的问题。 另外,Instant由于包含纳秒,在使用mysql的时候,要么用两个字段来分别存储,要么就舍去纳秒。 旧的Date对象也很方便进行转换:

Instant instant = Instant.now();
System.out.println(Date.from(instant));
Date date = new Date();
System.out.println(date.toInstant());

上述代码展示了如何在Instant和Date之间的转换。 另外java8种阿里规范有规定,拒绝在任何地方使用)java.sql.Date、java.sql.Time和java.sql.Timestamp。

因此很多博客上建议将Instant转换为java.sql.Date的方案实际上并不建议使用。 我们可以看看stackoverflow上关于Instant to mysql的问题。

How to store a Java Instant in a MySQL database 正确的回答解释到,我们无法将Instant的纳秒压缩到mysql数据库中的DateTime和timeStamp。在jdbc4.2以后的版本,可以忽略纳秒。

Instant instant = Instant().now().truncatedTo( ChronoUnit.MICROSECONDS ) ;

如果不进行压缩,那么可以采用两个字段存储:

long seconds = instant.getEpochSecond() ;
long nanos = instant.getNano() ;

再或者转为String类型之后,直接字符串存储。