在了解完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类型之后,直接字符串存储。