一、引言
Java 8 时间 API 诞生背景
在 Java 8 之前,旧的时间 API 存在着诸多问题,使得在处理日期和时间相关操作时面临不少困扰,这也凸显出 Java 8 引入新时间 API 的必要性。
首先,旧的时间 API 存在线程安全方面的隐患。例如,java.util.Date是非线程安全的,并且所有的日期类都是可变的,这堪称 Java 日期类最大的问题之一。在多线程环境下,如果多个线程同时对同一个Date对象进行操作、修改,很容易导致数据的不一致性以及难以预料的错误,开发者需要编写额外的代码来处理并发问题,比如加锁等操作,这无疑增加了开发的复杂性和工作量。
其次,API 设计较差。Java 的日期 / 时间类的定义并不统一、清晰,在java.util和java.sql的包中都有日期类,而且用于格式化和解析的类又在java.text包中定义。像java.util.Date同时包含日期和时间,而java.sql.Date却仅包含日期,将其纳入java.sql包并不合理,并且这两个类还拥有相同的名字,这样的设计容易让开发者在使用过程中产生混淆,使用起来颇为不便,也使得日常的日期和时间操作变得笨拙且容易出错。
再者,时区处理麻烦也是旧时间 API 的一大痛点。原本的日期类并不提供国际化支持,没有自带的时区功能,虽然 Java 后来引入了java.util.Calendar和java.util.TimeZone类来试图解决时区相关问题,但它们同样存在上述提到的那些问题,比如线程安全隐患以及设计不够合理等,在处理跨时区的日期和时间转换、计算等操作时,开发者往往需要编写复杂的额外逻辑来应对,很容易出现错误。
鉴于旧时间 API 存在的这些种种问题,Java 8 在java.time包下提供了很多新的 API,来帮助开发者更高效、准确且方便地处理日期和时间相关操作,比如有简化了日期时间处理且没有时区问题的 “Local(本地)” 相关 API,以及可以通过指定的时区处理日期时间的 “Zoned(时区)” 相关 API 等,为 Java 开发者带来了更优质的开发体验。
二、核心类总览
关键类概览
Java 8 新时间 API 中位于java.time包下有多个关键类,下面来简单介绍一下它们各自代表的含义及用途。
LocalDate:它是一个不可变的日期时间对象,只包含日期信息,通常可以视为年 - 月 - 日,不包含时间信息。例如生日、纪念日等只涉及具体日期的场景就可以用它来表示。它提供了操作和提取日期值的方法,同样也是线程安全的,开发者可以利用其相应方法方便地获取年、月、日等具体日期字段信息,像通过getYear()获取年份,getMonthValue()获取月份值,getDayOfMonth()获取当月的日数等。
LocalTime:表示时间,不包含日期和时区信息,属于不可变类。可以用于存储和操作每天的固定时间点,比如午夜、中午等。其提供了一系列方法来获取、设置和操作时、分、秒等时间的不同部分,以及进行比较、格式化等操作。例如通过LocalTime.now()能获取当前时间,LocalTime.of()方法则可以按照传入的小时、分钟、秒以及纳秒参数来创建一个指定的时间对象。
LocalDateTime:是一个不可变的日期时间对象,包含日期和时间信息,通常被视为年 - 月 - 日 - 时 - 分 - 秒,也能以纳秒精度表示时间,并且可以访问其他日期和时间字段,例如一年中的某天、一周中的某天和一周中的某周等。它常用于需要同时表示日期和时间,但不涉及时区的场景。像LocalDateTime.now()可获取当前的日期和时间(不含时区信息),LocalDateTime.of()能创建具有特定日期和时间的对象,在处理如记录某个具体时刻的业务场景中较为常用。
ZonedDateTime:这是一个包含时区和相对 UTC 或格林威治的时差的完整日期时间类,是最全面地表示日期时间及时区关系的类。在处理跨时区的业务场景,比如国际航班的起降时间、跨国会议的安排时间等,需要精确考虑到时区因素时,就可以使用它来进行日期时间的处理、转换以及对比等操作。
Instant:代表的是瞬时实例,也就是时间戳,它表示自 1970 年 1 月 1 日 00:00:00 UTC 以来的秒数和纳秒数。常用于记录某个特定瞬间,比如记录系统中某个事件发生的精确时刻,或者在不同时区之间转换时间时作为一个基准的时间点来使用。例如在处理与时间相关的数据时,若需要将 UTC 时间转换为本地时间等场景下会发挥作用。
这些核心类在 Java 8 新时间 API 中各司其职,帮助开发者能更高效、准确且方便地处理各种日期和时间相关操作,解决了旧时间 API 存在的诸多问题,极大地提升了开发体验。
三、常用类详细解析
(一)LocalDate 的使用
1. 创建方式
在 Java 8 中,LocalDate 的创建方式有多种,下面为大家详细介绍。
首先,可以通过now()方法获取当前系统时钟下的日期,示例代码如下:
LocalDate today = LocalDate.now();
System.out.println(today);
运行上述代码,就会输出当前的日期,格式如2023-08-15(实际输出会根据运行时的系统日期而定)。
另外,还能利用of()方法创建指定日期的 LocalDate 实例,它需要传入年、月、日这三个参数,示例如下:
LocalDate specificDate = LocalDate.of(2024, 1, 1);
System.out.println(specificDate);
这段代码会创建出代表2024年1月1日的 LocalDate 对象,并输出对应日期。
除此之外,parse()方法也可用于创建 LocalDate 实例,不过需要传入符合日期格式的字符串,比如:
LocalDate parsedDate = LocalDate.parse("2023-09-30");
System.out.println(parsedDate);
这里传入的字符串"2023-09-30"需要严格遵循yyyy-MM-dd这种 ISO 标准的日期格式,代码会将其解析并创建出对应的 LocalDate 对象。
2. 常用 API 方法
LocalDate 提供了许多实用的 API 方法,方便我们对日期进行各种操作。
例如获取日期信息方面,有获取年份的getYear()方法、获取月份值(1-12)的getMonthValue()方法、获取当月日数的getDayOfMonth()方法等,示例代码如下:
LocalDate date = LocalDate.of(2023, 8, 15);
int year = date.getYear();
int month = date.getMonthValue();
int day = date.getDayOfMonth();
System.out.println("年份: " + year + ", 月份: " + month + ", 日数: " + day);
运行后会输出对应日期的年份、月份以及日数信息。
在日期运算上,可以使用plus()方法来增加日期,minus()方法来减少日期,比如:
LocalDate baseDate = LocalDate.of(2023, 8, 15);
LocalDate nextWeekDate = baseDate.plusWeeks(1);
LocalDate lastMonthDate = baseDate.minusMonths(1);
System.out.println("一周后的日期: " + nextWeekDate);
System.out.println("一个月前的日期: " + lastMonthDate);
上述代码分别演示了获取当前日期一周后以及一个月前的日期情况。
判断日期先后可以使用isBefore()、isAfter()以及equals()方法,示例如下:
LocalDate date1 = LocalDate.of(2023, 8, 15);
LocalDate date2 = LocalDate.of(2023, 8, 10);
System.out.println("date1是否在date2之后: " + date1.isAfter(date2));
System.out.println("date1是否在date2之前: " + date1.isBefore(date2));
System.out.println("date1和date2是否相等: " + date1.equals(date2));
这段代码可以判断两个日期之间的先后以及相等关系。
还有测试闰年的isLeapYear()方法,示例为:
LocalDate leapYearDate = LocalDate.of(2024, 2, 1);
LocalDate nonLeapYearDate = LocalDate.of(2023, 2, 1);
System.out.println("2024年是否为闰年: " + leapYearDate.isLeapYear());
System.out.println("2023年是否为闰年: " + nonLeapYearDate.isLeapYear());
通过该方法能够判断给定的年份对应的日期是否处于闰年当中。
(二)LocalTime 的使用
1. 创建途径
创建 LocalTime 实例同样有几种常用方式。
一是从系统时钟创建,通过调用LocalTime.now()方法即可获取当前时间,示例代码如下:
LocalTime currentTime = LocalTime.now();
System.out.println(currentTime);
运行代码会输出类似14:30:20.123456789格式的当前时间(实际输出根据运行时系统时间而定)。
另外,也可以借助parse()方法通过解析符合格式的字符串来创建 LocalTime 实例,比如:
LocalTime parsedTime = LocalTime.parse("13:45:00");
System.out.println(parsedTime);
这里传入的字符串"13:45:00"要遵循HH:mm:ss这种时间格式要求,代码就能解析并创建出对应的 LocalTime 对象。
还能使用of()方法,按照传入的小时、分钟、秒以及纳秒(纳秒可省略,默认是 0)参数来创建一个指定的时间对象,示例如下:
LocalTime specificTime = LocalTime.of(15, 30, 0);
System.out.println(specificTime);
上述代码会创建出代表15时30分0秒的 LocalTime 对象。
2. API 功能展示
LocalTime 类中包含了不少实用的 API。
比如获取特定时间单位的方法,像getHour()获取小时数、getMinute()获取分钟数、getSecond()获取秒数等,示例代码如下:
LocalTime time = LocalTime.of(16, 45, 30);
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();
System.out.println("小时: " + hour + ", 分钟: " + minute + ", 秒: " + second);
这段代码可以分别获取并输出给定时间的小时、分钟和秒的数值。
比较时间先后可以使用isBefore()和isAfter()方法,示例如下:
LocalTime time1 = LocalTime.of(10, 30, 0);
LocalTime time2 = LocalTime.of(11, 0, 0);
System.out.println("time1是否在time2之前: " + time1.isBefore(time2));
System.out.println("time1是否在time2之后: " + time1.isAfter(time2));
通过这两个方法能够方便地判断两个时间的先后顺序。
此外,LocalTime 还提供了一些获取特殊时间常量的方法,像LocalTime.MIDNIGHT代表午夜 0 点整(00:00:00),LocalTime.NOON代表中午 12 点整(12:00:00),示例代码如下:
System.out.println("午夜时间: " + LocalTime.MIDNIGHT);
System.out.println("中午时间: " + LocalTime.NOON);
运行代码就可以输出对应的特殊时间常量。
(三)LocalDateTime 的使用
1. 实例化方法
创建 LocalDateTime 实例的操作方式较为多样。
首先,可以通过系统时钟获取,调用LocalDateTime.now()方法就能得到当前的日期和时间(不含时区信息),示例如下:
LocalDateTime currentDateTime = LocalDateTime.now();
System.out.println(currentDateTime);
运行代码会输出类似
2023-08-15T15:10:30.123456789格式的当前日期时间(实际根据运行时系统情况而定)。
使用工厂方法of()也能创建实例,需要传入年、月、日、时、分、秒等参数,示例代码如下:
LocalDateTime specificDateTime = LocalDateTime.of(2024, 1, 1, 10, 30, 0);
System.out.println(specificDateTime);
这段代码会创建出代表2024年1月1日10时30分0秒的 LocalDateTime 对象。
还可以通过parse()方法,传入符合格式的字符串来创建,例如:
LocalDateTime parsedDateTime = LocalDateTime.parse("2023-09-30T14:45:00");
System.out.println(parsedDateTime);
这里传入的字符串"2023-09-30T14:45:00"需要遵循yyyy-MM-ddTHH:mm:ss这种日期时间格式要求,代码就能解析并创建出对应的 LocalDateTime 对象。
2. 常用操作 API
LocalDateTime 支持诸多实用的操作 API。
在时间运算方面,比如使用plus()方法可以增加时间单位,minus()方法可以减少时间单位,示例如下:
LocalDateTime baseDateTime = LocalDateTime.of(2023, 8, 15, 15, 0, 0);
LocalDateTime twoHoursLater = baseDateTime.plusHours(2);
LocalDateTime oneDayBefore = baseDateTime.minusDays(1);
System.out.println("两小时后的时间: " + twoHoursLater);
System.out.println("一天前的时间: " + oneDayBefore);
上述代码演示了如何对 LocalDateTime 对象进行时间的加减操作,获取指定时间之后或者之前的日期时间。
另外,它还有提取特定单位的 Getter 方法,像toLocalDate()方法可以提取出日期部分,返回一个 LocalDate 对象,toLocalTime()方法可以提取出时间部分,返回一个 LocalTime 对象,示例如下:
LocalDateTime combinedDateTime = LocalDateTime.of(2023, 8, 15, 16, 30, 0);
LocalDate extractedDate = combinedDateTime.toLocalDate();
LocalTime extractedTime = combinedDateTime.toLocalTime();
System.out.println("提取的日期: " + extractedDate);
System.out.println("提取的时间: " + extractedTime);
通过这些 Getter 方法能够方便地从 LocalDateTime 对象中获取我们想要的日期或者时间部分信息,便于后续进一步的处理和使用。
四、时区处理相关类
ZoneId 与 ZonedDateTime
1. ZoneId 的创建与获取
在 Java 8 的时间 API 中,处理时区相关操作离不开ZoneId类。要创建ZoneId对象,我们可以使用ZoneId.of()方法,通过传入合法的 “区域 / 城市” 字符串来指定特定的时区,例如:
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
System.out.println(zoneId);
上述代码就创建了代表 “亚洲 / 上海” 时区的ZoneId对象,并进行输出展示。
另外,还可以通过ZoneId.systemDefault()方法来获取系统默认的时区,示例代码如下:
ZoneId defaultZoneId = ZoneId.systemDefault();
System.out.println(defaultZoneId);
这在需要获取当前所在环境默认时区设置的场景下会很实用。
如果想要获取所有合法的 “区域 / 城市” 字符串(也就是可用的时区标识),可以通过
ZoneId.getAvailableZoneIds()方法来实现,以下是示例代码:
Set availableZoneIds = ZoneId.getAvailableZoneIds();
for (String zoneIdStr : availableZoneIds) {
System.out.println(zoneIdStr);
}
这段代码会将所有合法的时区标识依次打印输出,方便我们知晓可选用的时区选项。
2. ZonedDateTime 的构成与应用
ZonedDateTime是一个功能强大的类,它由LocalDateTime和ZoneId共同构成,能够精准地表示包含时区信息的日期时间。
将LocalDate、LocalTime或LocalDateTime对象转化为ZonedDateTime对象是比较常见的操作。例如,若已有一个LocalDateTime对象,想要为其添加时区信息转化为ZonedDateTime,可以这样做:
LocalDateTime localDateTime = LocalDateTime.of(2023, 8, 15, 15, 30, 0);
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zoneId);
System.out.println(zonedDateTime);
上述代码先创建了一个LocalDateTime对象代表具体的日期时间,然后指定了 “亚洲 / 上海” 的ZoneId,通过ZonedDateTime.of()方法将两者结合,生成了对应的带有时区信息的ZonedDateTime对象并输出展示。
再比如,从LocalDate和LocalTime来构建ZonedDateTime,示例如下:
LocalDate localDate = LocalDate.of(2024, 1, 1);
LocalTime localTime = LocalTime.of(10, 0, 0);
ZoneId zoneId = ZoneId.of("Europe/London");
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDate, localTime, zoneId);
System.out.println(zonedDateTime);
这段代码先是分别创建了LocalDate和LocalTime对象,然后选取了 “欧洲 / 伦敦” 的时区ZoneId,利用ZonedDateTime.of()方法构造出了完整的带时区日期时间对象并进行输出。
在实际应用中,比如处理国际业务场景下不同地区的时间安排时,ZonedDateTime就能发挥重要作用。例如在一个跨国项目中,需要协调不同时区的团队成员开会,我们可以这样操作:
ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai");
ZoneId newYorkZoneId = ZoneId.of("America/New_York");
LocalDateTime meetingDateTimeInShanghai = LocalDateTime.of(2023, 10, 15, 14, 0, 0);
ZonedDateTime zonedMeetingDateTimeInShanghai = ZonedDateTime.of(meetingDateTimeInShanghai, shanghaiZoneId);
ZonedDateTime zonedMeetingDateTimeInNewYork = zonedMeetingDateTimeInShanghai.withZoneSameInstant(newYorkZoneId);
System.out.println("上海开会时间: " + zonedMeetingDateTimeInShanghai);
System.out.println("纽约对应的开会时间: " + zonedMeetingDateTimeInNewYork);
在这个示例中,先设定了 “亚洲 / 上海” 和 “美洲 / 纽约” 两个不同的时区,然后以上海的某个本地日期时间为基础创建了带时区的ZonedDateTime对象,接着通过withZoneSameInstant()方法,将其转换为纽约对应的带时区日期时间,方便知晓在不同时区下会议对应的时间安排,清晰地展示了ZonedDateTime在处理跨时区日期时间上的应用场景。
五、时间计算与比较相关类
(一)Instant 类
1. 创建 Instant 实例
在 Java 8 中,Instant 类提供了多种工厂方法来创建其实例。
首先,通过now()方法可以获取当前的瞬时时间,也就是获取当前时刻对应的 Instant 实例,示例代码如下:
Instant currentInstant = Instant.now();
System.out.println(currentInstant);
运行上述代码,会输出类似
2023-09-10T12:30:15.123456789Z这样的格式,最后的Z表示这是基于 UTC(协调世界时)的时间。
另外,还可以使用ofEpochSecond()方法来创建 Instant 实例,它接收两个参数,第一个参数是从 1970 年 1 月 1 日 00:00:00 UTC 开始到指定时间所经过的秒数,第二个参数是纳秒数(可选,默认为 0),示例如下:
Instant specificInstant = Instant.ofEpochSecond(1609459200); // 这里假设传入对应某个时间的秒数
System.out.println(specificInstant);
这段代码就会按照传入的秒数创建出对应的 Instant 对象,并输出相应的时间戳表示。
2. Instant 的时间计算与操作
Instant 类提供了一些便捷的方法用于时间计算与操作,能帮助我们推算出其他的 Instant 值。
例如,plusSeconds()方法可以在当前 Instant 实例的基础上增加指定的秒数,得到一个新的 Instant 实例,示例代码如下:
Instant baseInstant = Instant.now();
Instant futureInstant = baseInstant.plusSeconds(3600); // 在当前时间基础上加1小时(3600秒)
System.out.println("当前时间: " + baseInstant);
System.out.println("1小时后的时间: " + futureInstant);
与之对应的,minusSeconds()方法则可以减少指定的秒数,如下所示:
Instant anotherBaseInstant = Instant.now();
Instant pastInstant = anotherBaseInstant.minusSeconds(1800); // 在当前时间基础上减30分钟(1800秒)
System.out.println("当前时间: " + anotherBaseInstant);
System.out.println("30分钟前的时间: " + pastInstant);
通过这些方法,我们可以很方便地对时间点进行推算,满足不同业务场景下对于瞬时时间的计算需求。
(二)Duration 和 Period 类
1. Duration 类的应用
Duration 类主要用于创建两个 Temporal 对象之间的时长。它有一个很常用的静态工厂方法between(),通过传入两个 Temporal 对象(比如两个 Instant 对象或者两个 LocalTime 对象等),就能计算出它们之间的时间间隔。
例如,计算两个 Instant 实例之间的时间间隔,示例代码如下:
Instant startInstant = Instant.now();
// 模拟一些耗时操作,这里可以用Thread.sleep等方式模拟延迟,此处简单等待1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant endInstant = Instant.now();
Duration duration = Duration.between(startInstant, endInstant);
System.out.println("时间间隔为: " + duration.getSeconds() + " 秒");
不过需要注意的是,between()方法要求传入的两个 Temporal 对象必须是同类型的,并且要具有合理的顺序(比如起始时间在前,结束时间在后),不然会出现不符合预期的结果或者抛出异常。它在计算像程序执行某段代码的耗时、两个特定时间点的间隔等场景中非常实用。
2. Period 类的功能
Period 类主要用于处理日期之间的差值,也就是针对年、月、日这些日期部分的计算。
一种常见的使用方式是通过传入两个 LocalDate 对象来得出它们之间的差值,示例代码如下:
LocalDate startDate = LocalDate.of(2023, 1, 1);
LocalDate endDate = LocalDate.of(2023, 3, 15);
Period period = Period.between(startDate, endDate);
System.out.println("相差年数: " + period.getYears());
System.out.println("相差月数: " + period.getMonths());
System.out.println("相差天数: " + period.getDays());
此外,Period 类还提供了预定义的of()方法来生成 Period 实例,比如Period.ofYears()、Period.ofMonths()、Period.ofDays()等,示例如下:
Period oneYearPeriod = Period.ofYears(1);
Period threeMonthsPeriod = Period.ofMonths(3);
Period tenDaysPeriod = Period.ofDays(10);
这些生成的 Period 实例可以用于在日期上进行增加或者减少操作,例如:
LocalDate baseDate = LocalDate.of(2023, 8, 15);
LocalDate newDate = baseDate.plus(oneYearPeriod).plus(threeMonthsPeriod).plus(tenDaysPeriod);
System.out.println("调整后的日期: " + newDate);
通过这样的方式,能方便地计算日期之间的距离以及对日期进行相应的调整操作,在处理如项目周期、生日间隔等涉及日期差值的场景中发挥作用。
六、总结与实践建议
新时间 API 优势总结
Java 8 新时间 API 对比旧的时间 API 有着诸多显著优势,为开发中处理时间相关需求带来了极大便利。
首先,在解决旧 API 痛点方面表现卓越。旧的时间 API 存在线程安全隐患,像java.util.Date是非线程安全的,可变的日期类在多线程环境下容易导致数据不一致及难以预料的错误,而 Java 8 新时间 API 中的核心类如LocalDate、LocalTime、LocalDateTime等都是不可变对象,天生具备线程安全性,能在多线程场景下安全使用,无需额外编写复杂的并发处理代码。
在功能实用性上,新 API 涵盖了丰富的类来满足不同的时间处理场景。例如LocalDate专注处理日期信息,LocalTime用于时间信息处理,LocalDateTime结合了日期和时间,ZonedDateTime则能很好地应对带时区的日期时间处理,还有Instant用于表示时间戳,Duration和Period分别处理时间间隔和日期间隔等,这些类分工明确,让开发者可以根据具体需求精准选择合适的工具进行开发。
易用性也是新时间 API 的一大亮点。它摒弃了旧 API 中设计不合理、容易混淆的部分,例如旧 API 中日期 / 时间类定义分散在不同包且不统一,而新 API 统一位于java.time包下,结构清晰。同时,其提供的方法更加直观、易于理解,像plus()、minus()等系列方法让日期时间的增减操作变得简单明了,并且很多类都有now()方法方便获取当前相应的日期时间,of()方法便于创建指定的实例,parse()方法可以从符合格式的字符串解析出对应对象,大大降低了开发的难度和复杂性。
总之,Java 8 新时间 API 以其诸多优势,成为了 Java 开发者在处理日期和时间相关操作时的得力助手,提升了开发效率和代码质量。
实践应用建议
在实际项目中应用 Java 8 新时间 API 时,以下这些建议可以帮助大家更好地运用它。
一是根据具体需求准确选择合适的类。比如只涉及单纯的生日、纪念日等日期记录场景,使用LocalDate就足够了;如果是记录每天固定的某个时间点,像店铺开门时间等,那么LocalTime是恰当之选;要是需要同时体现日期和时间且不涉及时区,LocalDateTime能满足需求;而一旦涉及到跨时区的业务场景,例如国际航班时间安排、跨国会议时间协调等,就应当选用ZonedDateTime来精准处理。
二要注意不同类之间的使用限制和转换关系。例如在进行时间运算时,LocalDate主要针对年、月、日这些日期精度的运算,LocalTime侧重于时、分、秒等时间精度的运算,LocalDateTime则可进行更全面的任意精度的时间相加减操作。并且在不同类型的日期时间对象之间转换时,要遵循相应的规则,比如将LocalDateTime结合ZoneId可转换为ZonedDateTime,利用Instant结合时区信息也能与LocalDateTime等进行相互转换等,了解这些有助于正确地处理时间数据。
此外,在格式化和解析日期时间对象时,推荐使用DateTimeFormatter类,它提供了灵活且线程安全的方式来按照指定格式进行操作,避免了旧 API 中SimpleDateFormat的线程安全问题以及格式化不规范等情况。
最后,多参考官方文档以及进行实际的代码测试。官方文档对于各个类的方法、参数以及使用示例都有详细说明,能够帮助我们深入理解和准确使用 API。同时,在实际项目中多进行测试,及时发现和解决因为对 API 理解不到位或者使用场景考虑不周全而出现的问题,这样可以让我们更加熟练、高效地在项目里运用 Java 8 新时间 API。
本文暂时没有评论,来添加一个吧(●'◡'●)