接前一篇 java基础-时间相关(篇一 java7)
这一篇我们讲一下java8中时间类的基本使用,顺便讲一下final修饰符的作用。
基于java7中时间类的诸多问题,在Java8中进行了改进
首先,还是一个常规使用的代码,可以和java7进行对比。
//@Test
public void java8DateTest(){
//日期
LocalDate date = LocalDate.now();
System.out.println(date.toString());
//output 2019-11-20
//分别获取年月日
System.out.printf("年=%d 月=%d 日=%d",date.getYear(),date.getMonthValue(),date.getDayOfMonth());
System.out.println(" ");
//output 年=2019 月=11 日=20
//时间
LocalTime time = LocalTime.now();
System.out.println(time.toString());
//output 17:58:08.508
LocalDateTime now = LocalDateTime.now();
System.out.println(now.toString());
//output 2019-11-20T18:17:24.565
System.out.println(now.toLocalDate());
//output 2019-11-20
System.out.println(now.toLocalTime());
//output 18:17:24.565
//日期和字符串转换
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 日期时间转字符串
String nowText = now.format(formatter);
System.out.println("nowText= " + nowText);
//output nowText= 2019-11-20 18:19:35
// 字符串转日期时间
String datetimeText = "2019-11-20 23:59:59";
LocalDateTime datetime = LocalDateTime.parse(datetimeText, formatter);
System.out.println(datetime);
//output 2019-11-20T23:59:59
long timeStamp = 1574245577429L;
Instant instant = Instant.ofEpochMilli(timeStamp);
String timeToStr = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).format(formatter);
System.out.println(timeToStr);
//output 2019-11-20 18:26:17
long timeStamp2 = now.toInstant(ZoneOffset.of("+8")).toEpochMilli();
System.out.println(timeStamp2);
//output 1574245919701
now = LocalDateTime.now();
System.out.println(now.toLocalTime());
}
上一篇我们讲到,java7的SimpleDateFormat
有线程安全的问题,java8里怎么解决的呢。
首先,如果不使用format,在java8里,直接把时间戳转化为字符串
@Test
public void timestampToStr(){
long times = System.currentTimeMillis();
Instant instant = Instant.ofEpochMilli(times);
ZoneId zone = ZoneId.systemDefault();
System.out.println(LocalDateTime.ofInstant(instant, zone));
}
得到的字符串是
2020-03-12T18:33:00.041
这个比java7的格式友好一些,基本能用了
如果需要format呢,添加一个format即可
@Test
public void timestampToStr(){
long times = System.currentTimeMillis();
Instant instant = Instant.ofEpochMilli(times);
ZoneId zone = ZoneId.systemDefault();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(formatter.format(LocalDateTime.ofInstant(instant, zone)));
}
得到的输出格式是
2020-03-12 18:38:37
首先,DateTimeFormatter
不会再犯 SimpleDateFormat
中的错误,放一个公共变量在里面,让不同的线程都能修改。DateTimeFormatter
中的公共变量,都使用了 final
修饰,final
修饰有啥用呢,我们写个demo来测试一下。
首先,不去思考final的正确用法,我们一步一步的来。
按照常规的思维,写一个类
public class FinalDemo {
private final Integer number;
public void setNumber(Integer number){
this.number = number;
}
}
这个写出来后,idea提示了两个错误
第一个是
private final Integer number;
提示错误:number没有被初始化
第二个是
this.number = number;
提示错误:不能给final变量number赋值。
根据上面的错误提示,我们知道
- final变量需要初始化。
- final变量在第一次赋值后,不能修改。
DateTimeFormatter
中的公共变量,都是final修饰的,首先保证了一点,在第一次赋值之后,不能修改。
我们继续研究一下final,既然说到这里,就把这个搞清楚。
我们修改一下demo。
public class FinalDemo {
public final Integer number = 3;
public Integer getNumber(){
return number;
}
}
然后,我们写一个测试方法,去使用它
@Test
public void finalTest(){
FinalDemo finalDemo = new FinalDemo();
System.out.println(finalDemo.number);
}
可以拿到number的值,我们在这里进行修改呢
@Test
public void finalTest(){
FinalDemo finalDemo = new FinalDemo();
System.out.println(finalDemo.number);
finalDemo.number = 5;
System.out.println(finalDemo.number);
}
会提示报错
Error:(95, 18) java: 无法为最终变量number分配值
也就是final的值,在第一次赋值后,无法修改。
接下来,我们修改一下代码
public class FinalNumberDemo {
private final Integer number;
FinalNumberDemo(Integer number){
this.number = number;
}
public Integer getNumber(){
return this.number;
}
public void setNumber(Integer number){
this.number = number;
}
}
这里,我们在构造函数里给number赋值,这里,没有在声明number的地方报错,因为在构造函数中赋值,就保证了number在使用的时候,就已经有初始值了。
同时,我们加入了get和set方法。在idea中,会提示set方法有错,不能改变final变量的值,我们不管,写一个方法来调用看看。
@Test
public void finalTest(){
FinalNumberDemo finalNumberDemo = new FinalNumberDemo(3);
System.out.println(finalNumberDemo.getNumber());
finalNumberDemo.setNumber(4);
System.out.println(finalNumberDemo.getNumber());
}
执行起来也是会报错的。
好了,我们对final的特性有了一些了解,继续看下面的demo。
首先,我们创建一个用户类
public class User {
private Long id;
private String name;
User(Long id,String name){
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
非常简单的一个类,记录一个用户的信息。
然后,我们用final来声明它。
public class FinalDemo {
private final User user;
FinalDemo(User user){
this.user = user;
}
public User getUser(){
return this.user;
}
}
在这个demo中,我们在构造函数里给user赋值,符合final的要求。
我们现在来使用这个demo
@Test
public void finalTest(){
User user = new User(1L,"Joe");
FinalDemo finalDemo = new FinalDemo(user);
User somebody = finalDemo.getUser();
System.out.println(somebody.getId());
System.out.println(somebody.getName());
System.out.println("==========magic=========");
user.setName("Li");
System.out.println(somebody.getId());
System.out.println(somebody.getName());
}
我们先设置一个user进去,然后,修改user自己,没有去修改demo类里面的user,结果会怎么样呢
1
Joe
==========magic=========
1
Li
user的名字被修改了。
为什么呢?
前面我们确定了final保证他指向的数据在第一次赋值之后是不可修改的
但是,引用类型,本身存储的是实际数据的地址。
我们如果将引用类型设定为final,只保证了这个地址不变,但是地址指向的内容是否变动,是保证不了的。
用图来简单说明一下
首先,创建一个userA对象
在userA这个变量里面存储的,是实际数据的地址。
如果这个时候,我们通过
=
的方式,赋值给userB
User userA = new User();
User userB = userA;
这样操作,实际上是把userA里存的数据地址赋值给了userB,也就是说,userA和userB指向的是相同的数据
Talk is cheap. Show me the code.
User userA = new User();
userA.setId(1L);
userA.setName("John");
User userB = userA;
System.out.println("userB id==>"+userB.getId());
System.out.println("userB name==>"+userB.getName());
System.out.println("================");
userA.setName("John Snow");
System.out.println("userB id==>"+userB.getId());
System.out.println("userB name==>"+userB.getName());
输出结果是:
userB id==>1
userB name==>John
================
userB id==>1
userB name==>John Snow
我们修改A的名字,B的名字跟着改了,这下明白了吧。
好了,引用类型的大致基本原理我们搞清楚了。
当我们用final来修饰引用类型的时候,保证的是,这个数据地址不可变,无法保证实际数据被修改。如果不了解这一点,可能会搞出bug。
好了。java8的时间类,还有很多用法以及细节,不清楚的可以查看文档或者百度。我们这里结合java7和java8的时间类进行对比,实际更多的是希望大家了解一下线程安全性相关问题,在写代码的时候,有这么一个概念,不致于写出bug之后,找不到原因。如果后面有时间,我会继续写线程相关的文章。