還在用SimpleDateFormat?Java8都發布N年了,轉LocalDateTime吧

happyjava發表於2019-06-17

前言

Java8釋出,已有數年之久,但是發現很多人都還是堅持著用SimpleDateFormat和Date進行時間操作。SimpleDateFormat這個類不是執行緒安全的,在使用的時候稍不注意,就會產生致命的問題。Date這個類,是可以重新設定時間的,這對於一些類內部的屬性來說,是非常不安全的。

SimpleDateFormat是執行緒不安全的類

在阿里巴巴規約手冊裡,強制規定SimpleDateFormat是執行緒不安全的類,當定義為靜態變數時,必須加鎖處理。忽略執行緒安全問題,正是大多數Java初學者在進行時間轉化時容易踩坑的點。

還在用SimpleDateFormat?Java8都發布N年了,轉LocalDateTime吧

Date屬性可以重新設定時間

比如有User.java如下:

public class User {
​
 private String username;
​
 private Date birthday;
​
 public User(String username, Date birthday) {
 this.username = username;
 this.birthday = birthday;
 }
​
 public String getUsername() {
 return username;
 }
​
 public void setUsername(String username) {
 this.username = username;
 }
​
 public Date getBirthday() {
 return birthday;
 }
​
 public void setBirthday(Date birthday) {
 this.birthday = birthday;
 }
}
複製程式碼

我們例項化該User

public class Main {
​
 public static void main(String[] args) {
 User user = new User("happyjava", new Date());
 }
​
}
複製程式碼

這當然沒什麼問題,但是我可以通過user.getBirthday()方法獲取到birthday的引用,從而修改直接修改birthday的值。如下:

public static void main(String[] args) {
 User user = new User("happyjava", new Date());
 System.out.println(user.getBirthday());
 Date birthday = user.getBirthday();
 birthday.setTime(11111111L);
 System.out.println(user.getBirthday());
}
複製程式碼

輸出結果如下:

還在用SimpleDateFormat?Java8都發布N年了,轉LocalDateTime吧

這裡可以看到,user物件的birthday屬性被修改掉了。這也是Date物件的弊端所在,我們可以通過改寫getter方法,使它返回一個新的Date物件即可解決,如下:

 public Date getBirthday() {
// return birthday;
 return new Date(birthday.getTime());
 }
複製程式碼

切記這裡是不可以用clone方法來生成返回一個新的Date物件的,因為Date類可以被繼承,你不能確定呼叫者是否給birthday設定了一個Date的子類。

Java8提供的新的時間類庫LocalDateTime

Java8提供了LocalDateTime來替代傳統的Date來處理時間,下面,我們就來探討下這個類庫的使用方法吧。

1.獲取當前時間

可以通過 LocalDateTime localDateTime = LocalDateTime.now();方法來獲取當前時間,測試如下:

@Test
public void testNow() {
 LocalDateTime localDateTime = LocalDateTime.now();
 System.out.println(localDateTime);
}
複製程式碼

輸出結果

2019-05-06T22:25:07.309
複製程式碼

2.根據時間戳初始化時間

@Test
public void testNewFromTimestamp() {
 Instant instant = Instant.ofEpochMilli(System.currentTimeMillis());
 LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.of("+8"));
 System.out.println(dateTime);
}
複製程式碼

這裡的+8意思是東八區,下同。

輸出結果:

2019-05-06T22:27:34.567
複製程式碼

3.根據字串獲取時間

可以使用LocalDateTime.parse方法對字串進行轉化成時間,如果不傳pattern,預設是2019-05-06T11:16:12.361格式。

@Test
public void testNewFromString() {
 // 1.預設格式 2019-05-06T11:16:12.361
 String dateStr = "2019-05-06T11:16:12.361";
 LocalDateTime localDateTime = LocalDateTime.parse(dateStr);
 System.out.println(localDateTime);
 // 2. 自定義格式
 String pattern = "yyyy-MM-dd HH:mm:ss";
 dateStr = "2019-01-01 12:12:12";
 localDateTime = LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
 System.out.println(localDateTime);
}
複製程式碼

輸出結果:

2019-05-06T11:16:12.361
2019-01-01T12:12:12
複製程式碼

4.時間轉化成字串

可以通過DateTimeFormatter的format方法,將LocalDateTime轉化成字串。

@Test
public void testToString() {
 LocalDateTime now = LocalDateTime.now(ZoneId.of("+8"));
 String pattern = "yyyy-MM-dd HH:mm:ss";
 DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
 String format = formatter.format(now);
 System.out.println(format);
}
複製程式碼

輸出結果:

2019-05-06 22:33:03
複製程式碼

5.LocalDateTime轉時間戳

@Test
public void testDateToTimeMillis() {
 LocalDateTime dateTime = LocalDateTime.now();
 long epochMilli = dateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
 System.out.println(epochMilli);
}
複製程式碼

輸出結果:

1557153504304
複製程式碼

總結

因為DateTimeFormatter是執行緒安全的,所以在實際使用LocalDateTime的時候,可以把DateTimeFormatter定義成靜態常量的方式進行使用。以上列舉了比較常用的時間操作,LocalDateTime還可以做很多事情,這個就讓讀者自行去挖掘吧。我自己封裝了個LocalDateTime工具類,只做過簡單的自測,大家可以參考一下:

package happy.localdatetime;
​
​
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
​
/**
 * @author Happy
 */
public class DateTimeUtils {
​
 private DateTimeUtils() {
 }
​
 private final static String COMMON_PATTERN = "yyyy-MM-dd HH:mm:ss";
​
 private final static DateTimeFormatter COMMON_FORMATTER = DateTimeFormatter.ofPattern(COMMON_PATTERN);
​
 private final static ZoneOffset DEFAULT_ZONE_OFFSET = ZoneOffset.of("+8");
​
 /**
 * 預設 yyyy-MM-dd HH:mm:ss 格式
 */
 public static String dateToString(LocalDateTime dateTime) {
 assert dateTime != null;
 return COMMON_FORMATTER.format(dateTime);
 }
​
 /**
 * 預設 yyyy-MM-dd HH:mm:ss 格式
 */
 public static LocalDateTime stringToDate(String dateStr) {
 assert dateStr != null;
 return LocalDateTime.parse(dateStr, COMMON_FORMATTER);
 }
​
 public static String dateToString(LocalDateTime dateTime, DateTimeFormatter formatter) {
 assert dateTime != null;
 return formatter.format(dateTime);
 }
​
 public static LocalDateTime stringToDate(String dateStr, DateTimeFormatter formatter) {
 assert dateStr != null;
 return LocalDateTime.parse(dateStr, formatter);
 }
​
 public static long dateToTimeMillis(LocalDateTime dateTime) {
 assert dateTime != null;
 return dateTime.toInstant(DEFAULT_ZONE_OFFSET).toEpochMilli();
 }
​
 public static LocalDateTime timeMillisToDate(long timeMillis) {
 Instant instant = Instant.ofEpochMilli(timeMillis);
 return LocalDateTime.ofInstant(instant, DEFAULT_ZONE_OFFSET);
 }
​
 public static void main(String[] args) {
 String s = dateToString(LocalDateTime.now());
 System.out.println(s);
 System.out.println();
 String dateStr = "2019-01-01 12:12:12";
 LocalDateTime localDateTime = stringToDate(dateStr);
 System.out.println(localDateTime);
 System.out.println();
 System.out.println(dateToTimeMillis(localDateTime));
 System.out.println();
 System.out.println(timeMillisToDate(System.currentTimeMillis()));
 }
​
}
複製程式碼

相關文章