JDK8之前的日期时间API

System类中获取时间戳的方法

System类提供的public static long currentTimeMillis() 当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。

此方法适于计算时间差

例如我想测试不同排序算法对10W个数据排序的时间

就可以在执行排序算发之前获取一下当前毫秒数

等排序算法执行完毕后,再获取一下当前毫秒数

二者只差即为排序算法执行时间

1
2
3
4
5
@Test
public void test1() {
long time = System.currentTimeMillis();
System.out.println(time);
}

Java中两个Date类的使用

java.util.Date类和java.sql.Date类

  1. 两个构造器的使用
    • Date() 创建一个对应当前时间的Date对象
    • Date(long time) 创建一个指定毫秒数的Date对象
  2. 两个方法的使用
    • toString() 显示当前时间的 年,月,日,时,分,秒
    • getTime() 获取当前Date对象对应的毫秒数(时间戳)
  3. java.sql.Date对应着数据库中的日期类型变量
    • 如何实例化
    • 如何将java.util.Date对象转化为java.sql.Date对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void test2() {
//构造器一:Date():创建一个对应当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString()); //toString()可以省略 Fri Apr 15 16:57:32 CST 2022
System.out.println(date1.getTime()); //当前毫秒数 1650013052397

//构造器二:创建指定毫秒数的Date对象
Date date2 = new Date(1650045342397L);
System.out.println(date2);

//创建java.sql.Date对象
java.sql.Date date3 = new java.sql.Date(1650045342397L);
System.out.println(date3);

//如何将java.util.Date对象转化为java.sql.Date
//方式一
Date date4 = new java.sql.Date(1650045342397L);
java.sql.Date date5 = (java.sql.Date) date4;

//方式二 常用
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());
}

SimpleDateFormat的使用

  • Date类的API不易于国际化,大部分都被废弃了,java.text.SimpleDateFormat类是一个不与语言环境有关的方式来格式化和解析日期的具体类

  • 它允许执行

    • 格式化: 日期 --> 文本
    • 解析: 文本 --> 日期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test3() throws ParseException {
//格式化日期 --> 字符串
SimpleDateFormat sdf = new SimpleDateFormat();
Date date = new Date();
System.out.println(date); //Fri Apr 15 18:34:46 CST 2022
System.out.println(sdf.format(date)); //22-4-15 下午6:34

//解析:格式化的逆过程 字符串 --> 日期
String str = "20-12-18 上午8:26";
Date date1 = sdf.parse(str); //这里要用try/catch包起来或者抛异常
System.out.println(date1); //Fri Dec 18 08:26:00 CST 2020

//指定样式的格式化
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
System.out.println(sdf1.format(date)); //2022.04.15 06:34:19

//指定样式的解析
Date date2 = sdf1.parse("2077.08.26 23:12:13");
System.out.println(date2); //Thu Aug 26 11:12:13 CST 2077
}

注意大小写:大写Y表示weekYear 大写H表示24小时制 小写h表示12小时制
详情参阅API文档Class SimpleDateFormat

SimpleDateFormat的练习

练习一: 字符串"2020-09-08"转换为java.sql.Date

1
2
3
4
5
6
7
8
@Test
public void test4() throws ParseException {
String str = "2020-09-08";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse(str);
java.sql.Date date1 = new java.sql.Date(date.getTime());
System.out.println(date1);
}

练习二: 三天打鱼两天晒网
从1990.01.01开始 三天打鱼两天晒网
那么在2022.04.14时 是在打鱼还是晒网

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 计算出相差多少天之后 对5整除 如果结果是1,2,3那么就是在打鱼 否则在晒网
@Test
public void test5() throws ParseException {
String str = "1990.01.01";
String str1 = "2022.04.14";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd");
Date startDate = sdf.parse(str);
Date endDate = sdf.parse(str1);
long a = (endDate.getTime() - startDate.getTime()) / (1000 * 24 * 60 * 60) + 1;
long b = a % 5;
if (b == 1 || b == 2 || b == 3) System.out.println("打鱼");
else System.out.println("晒网");
}

Calendar类的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Test
public void test6() {
//1. 实例化
//方式一:创建其子类 GregorianCalendar()的对象
//方式二:调用其静态方法getInstance() ---> 常用 实际上还是创建的GregorianCalendar类的实例
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getClass()); //class java.util.GregorianCalendar

//今天是2022.4.16 星期六
//int get(int field) 返回给定日历字段的值
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days); //16

days = calendar.get(Calendar.DAY_OF_YEAR);
System.out.println(days); //106

days = calendar.get(Calendar.DAY_OF_WEEK);
System.out.println(days); //7 星期日是第1天 星期一是第2天 以此类推 星期六是第7天

int months = calendar.get(Calendar.MONTH);
System.out.println(months); //3 一月份是0 以此类推 十二月份是11

//void set(int field,int value) 将给定的日历字段设置为给定值。
calendar.set(Calendar.DAY_OF_MONTH, 13);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days); //13

//void add() 根据日历的规则,将指定的时间量添加或减去给定的日历字段
calendar.add(Calendar.DAY_OF_MONTH, 10);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days); //13 + 10 = 23

//Date getTime() 日历类---> Date 返回表示此 Calendar的时间值(距离 Epoch的毫秒偏移量)的 Date对象。
Date date = calendar.getTime();
System.out.println(date); //Sat Apr 23 12:36:04 CST 2022

//void setTime(Date date) Date ---> 日历类 使用给定的Date设置此日历的时间。
Date date1 = new Date();
calendar.setTime(date1);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days); //16
}

JDK8中日期时间API的介绍

新日期时间API出现的背景

如果我们可以跟别人说:“我们在1502643933071见面,别晚了!”那么就再简单不过了。但是我们希望时间与昼夜和四季有关,于是事情就变复杂了。JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar并不比Date好多少。它们面临的问题是:

  • 可变性:像日期和时间这样的类应该是不可变的。
  • 偏移性:Date中的年份是从1900开始的,而月份都从0开始。
  • 格式化:格式化只对Date有用,Calendar则不行。
  • 此外,它们也不是线程安全的;不能处理闰秒等。
1
2
3
4
5
6
7
8
9
@Test
public void test7() {
//偏移量
Date date = new Date(2022,4,16); //Tue May 16 00:00:00 CST 3922
System.out.println(date);
//年份从1900开始算起 月份从0开始算起
date = new Date(2022-1900,4-1,16); //Sat Apr 16 00:00:00 CST 2022
System.out.println(date);
}

第三次引入的API是成功的,并且Java 8中引入的java.time API 已经纠正了过去的缺陷,将来很长一段时间内它都会为我们服务。

Java 8 吸收了Joda-Time 的精华,以一个新的开始为Java 创建优秀的API。新的java.time 中包含了所有关于本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的Date 类新增了toInstant()方法,用于把Date 转换成新的表示形式。这些新增的本地化时间日期API 大大简化了日期时间和本地化的管理。

LocalDate、LocalTime、LocalDateTime的使用

LocalDate、LocalTime、LocalDateTime类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。

  • LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储生日、纪念日等日期。
  • LocalTime表示一个时间,而不是日期。
  • LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。

注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法,也就是公历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Test
public void test8() {
//获取年月日
System.out.println("获取年月日");
LocalDate localDate = LocalDate.now();
System.out.println(localDate);

//获取时分秒
System.out.println("获取时分秒");
LocalTime localTime = LocalTime.now();
System.out.println(localTime);

//获取年月日时分秒
System.out.println("获取年月日时分秒");
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);

//设置指定的年,月,日,时,分,秒 没有偏移量
System.out.println("设置指定的年,月,日,时,分,秒 没有偏移量");
LocalDateTime localDateTime1 = LocalDateTime.of(2077, 8, 26, 13, 14, 21);
System.out.println(localDateTime1);

//getXxx()
System.out.println("getXxx()");
System.out.println(localDateTime.getDayOfMonth()); //当前月的第几天
System.out.println(localDateTime.getDayOfWeek()); //星期几
System.out.println(localDateTime.getDayOfYear()); //一年的第几天
System.out.println(localDateTime.getMonth()); //月份
System.out.println(localDateTime.getMinute()); //分钟

//体现不可变性
//withXxx():设置相关属性
System.out.println("体现不可变性");
LocalDateTime localDateTime2 = localDateTime1.withDayOfMonth(10);
System.out.println(localDateTime1);
System.out.println(localDateTime2);
//plusXxx/minusXxx():在当前对象上加减日/年/月/周等
LocalDateTime localDateTime3 = localDateTime1.plusDays(10);
LocalDateTime localDateTime4 = localDateTime1.minusHours(8);
System.out.println(localDateTime3);
System.out.println(localDateTime4);
}
方法 描述
now() 静态方法,根据当前时间创建对象/指定时区的对象
of() 静态方法,根据指定日期/时间创建对象
getDayOfMonth()/getDayOfYear() 获得月份天数(1-31)/获得年份天数(1-366)
getDayOfWeek() 获得星期几(返回一个DayOfWeek枚举值)
getMonth() 获得月份,返回一个Month枚举值
getHour()/getMinute()/getSecond() 获得当前对象对应的小时、分钟、秒
withDayOfMonth()/withDayOfYear()/ withMonth()/withYear() 将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象
minusMonths()/minusWeeks()/minusDays()/ minusYears()/minusHours() 从当前对象减去几月、几周、几天、几年、几小时

Instant类的使用

  • Instant:时间线上的一个瞬时点。这可能被用来记录应用程序中的事件时间戳。

  • 在处理时间和日期的时候,我们通常会想到年,月,日,时,分,秒。然而,这只是时间的一个模型,是面向人类的。第二种通用模型是面向机器的,或者说是连续的。在此模型中,时间线中的一个点表示为一个很大的数,这有利于计算机处理。在UNIX中,这个数从1970年开始,以秒为的单位;同样的,在Java中,也是从1970年开始,但以毫秒为单位。

  • java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位。Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。

1秒 = 1000毫秒 = 10^6微秒 = 10^9纳秒

方法 描述
now() 静态方法,返回默认UTC时区的Instant类的对象
of() 静态方法,返回在1970-01-01 00:00:00基础上 加上指定毫秒数之后的Instant类的对象
atOffset(ZoneOffset offset) 结合即时的偏移来创建一个OffsetDateTime
toEpochMilli() 返回1970-01-0100:00:00到当前时间的毫秒数,即为时间戳

时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test9() {
//now()获取本初子午线对应的标准时间 与北京时间有8小时时差 因为北京在东八区
Instant instant = Instant.now();
System.out.println(instant);

//atOffset(ZoneOffset offset) 设置偏移量
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);

//toEpochMilli():获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数 ---> Date类的getTime()
long milli = instant.toEpochMilli();
System.out.println(milli);

//ofEpochMilli():通过给定的毫秒数,获取Instant实例 -->Date(long millis)
Instant instant1 = Instant.ofEpochMilli(1650166369891L);
System.out.println(instant1);
}

DateTimeFormatter的使用

java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:

  1. 预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
  2. 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
  3. 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

依旧是自定义格式最常用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Test
public void test10() {
//方式一
System.out.println("方式一");
DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//格式化:日期 ---> 字符串
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
System.out.println(dateTimeFormatter1.format(localDateTime));

//解析:字符串 ---> 日期
TemporalAccessor parse = dateTimeFormatter1.parse("2077-01-01T00:00:00.000");
System.out.println(parse);

//方式二
System.out.println("方式二");
DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
System.out.println(dateTimeFormatter2.format(localDateTime));

//方式三 自定义格式ofPattern("yyyy-MM-dd hh:mm:ss")
System.out.println("方式三");
//格式化
DateTimeFormatter dateTimeFormatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
System.out.println(dateTimeFormatter3.format(localDateTime));
//解析
TemporalAccessor parse1 = dateTimeFormatter3.parse("2077-01-17 12:52:47");
System.out.println(parse1);
}

Java比较器

概述

Java中的对象,正常情况下,只能进行比较:==或 != 。不能使用 >或<的,但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。 如何实现?使用两个接口中的任何一个:Comparable或 Comparator

Java实现对象排序的方式有两种:

  • 自然排序:java.lang.Comparable
  • 定制排序:java.util.Comparator

Comparable自然排序举例

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test11() {
String[] arr = new String[]{"AA", "GG", "EE", "FF", "DD", "CC", "BB"};
//默认升序排序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));

Integer[] nums = new Integer[]{9, 4, 5, 7, 8, 2, 1, 3, 6};
//默认从小到大
Arrays.sort(nums);
System.out.println(Arrays.toString(nums));

}

自定义类实现Comparable自然排序

对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。在compareTo(obj)方法中指明如何排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//自定义一个商品类实现Comparable接口,重写compareTo()方法,按照商品价格升序排序
public class Goods implements Comparable {
String name;
double price;

public Goods(String name, double price) {
this.name = name;
this.price = price;
}

public Goods() {
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
//指明商品比较大小的方式:按照价格从低到高排序
@Override
public int compareTo(Object o) {
if (o instanceof Goods) {
Goods goods = (Goods) o;
return Double.compare(this.getPrice(),goods.getPrice());
}
throw new RuntimeException("输入数据类型不一致");
}
}

@Test
public void test12() {
Goods[] goods = new Goods[4];
goods[0] = new Goods("联想", 20);
goods[1] = new Goods("小米", 50);
goods[2] = new Goods("华为", 40);
goods[3] = new Goods("罗技", 30);

Arrays.sort(goods);
System.out.println(Arrays.toString(goods));

}

使用Comparator实现定制排序

  1. 背景:当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序

  2. 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Test
public void test13() {
String[] arr = new String[]{"AA", "GG", "EE", "FF", "DD", "CC", "BB"};
//重写方法变成降序排序
Arrays.sort(arr, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
if (o1 instanceof String && o2 instanceof String) {
return -o1.compareTo(o2);
}
throw new RuntimeException("输入的数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));

Integer[] nums = new Integer[]{9, 4, 5, 7, 8, 2, 1, 3, 6};
Arrays.sort(nums, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return -o1.compareTo(o2);
}
});
System.out.println(Arrays.toString(nums));

Goods[] goods = new Goods[4];
goods[0] = new Goods("联想", 20);
goods[1] = new Goods("小米", 50);
goods[2] = new Goods("华为", 40);
goods[3] = new Goods("罗技", 30);

Arrays.sort(goods, new Comparator<Goods>() {
@Override
public int compare(Goods o1, Goods o2) {
return -o1.compareTo(o2);
}
});
System.out.println(Arrays.toString(goods));
}

Comparable接口与Comparator的使用的对比

  • Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。

  • Comparator接口属于临时性的比较。每次比较时都需要重写一遍compate方法。