java基础-时间相关(篇二 java8)

接前一篇 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赋值。

根据上面的错误提示,我们知道

  1. final变量需要初始化。
  2. 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对象

image.png

在userA这个变量里面存储的,是实际数据的地址。
如果这个时候,我们通过= 的方式,赋值给userB

        User userA = new User();
        User userB = userA;

这样操作,实际上是把userA里存的数据地址赋值给了userB,也就是说,userA和userB指向的是相同的数据


image.png

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之后,找不到原因。如果后面有时间,我会继续写线程相关的文章。

你可能感兴趣的:(java基础-时间相关(篇二 java8))