java的类有啥缺陷_Java 有哪些不好的设计?

Java的优点很多,缺点也不少,下面只说日期API的问题。

一、Jdk7及以前的日期时间类的不方便使用问题

使用Java日期时间类,每个人都很熟悉每个项目中必不可少的工具类就是DateUtil,包含各种日期计算,格式化等处理,而且常常会遇到找不到可用的处理方法,需要自己新增方法,处理过程很复杂。

1.Date中的过时方法等

Date中的方法一般都过时了,不建议使用,有一些歧义。比如:

(1)new Date(2019,01,01)实际是3919年2月。因为Date的构造函数 的年份表示的始于1900年的差值。

(2)month是从0开始的。

(3)Date如果不格式化,打印出的日期可读性差。

Fri Dec 13 23:08:12 CST 2019

(4)Date和java.sql.Date命名完全一样,不易区分,引入包时经常需要选择。

2 Calendar操作繁琐、不支持复杂计算等

Calendar虽然能够处理大部分的Date计算,但设计不是很成功,一些简单操作都要多次调用。对一些复杂的计算比如两个日期之间有多少个月,生日计算年龄等都不支持。比如:

(1)DAY_OF_WEEK 的取值,是从周日(1)开始的。

Calendar calendar = Calendar.getInstance();

calendar.setTime(new Date());

System.out.println(calendar.get(Calendar.DAY_OF_WEEK));

(2)MONTH 的取值,是从0开始的。

Calendar calendar = Calendar.getInstance();

calendar.setTime(new Date());

System.out.println(calendar.get(Calendar.MONTH));

(3)set()方法延迟修改

通过set()方法设置某一个字段的值得时候,该字段的值不会立马修改,直到下次调用get()、getTime()等时才会重新计算日历的时间。延迟修改的优势是多次调用set()方法不会触发多次不必要的计算。下面程序演示了set()方法延迟修改的效果:

Calendar cal = Calendar.getInstance();

cal.set(2003,7,31);//2003-8-31//将月份设为9,但9月31不存在//如果立即修改,系统会把cal自动调整到10月1日cal.set(Calendar.MONTH,8);

//下面代码输出了10月1日System.out.println(cal.getTime());//(1)//设置DATE字段为5cal.set(Calendar.DATE, 5);//(2)System.out.println(cal.getTime());//(3)

打印结果为:

Wed Oct 01 22:25:41 CST 2003

Sun Oct 05 22:25:41 CST 2003

如果将(1)处的代码注释掉,打印结果为:

Fri Sep 05 22:28:06 CST 2003

你看明白了吗?如果将(1)处的代码注释掉,因为set()方法具有延迟性,它内部只是先记录下MONTH字段的值为8,接着程序将DATE字段设置为5,程序内部再次记录DATE字段的值为5——就是9月5日。

3.日期类并不提供国际化,没有时区支持,

因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

========================================================

二、Jdk7及以前的日期时间类的线程安全问题

1.Date类为可变的,在多线程并发环境中会有线程安全问题。

(1)可以使用锁来处理并发问题。

(2)使用JDK8 Instant 或 LocalDateTime替代。

2.Calendar的子类为可变的,在多线程并发环境中会有线程安全问题。

(1)可以使用锁来处理并发问题。

(2)使用JDK8 LocalDateTime 替代。

3.DateFormat和SimpleDateFormat不是线程安全的原因

(1)DateFormat中calendar是共享变量,其子类SimpleDateFormat中也是共享变量。

DateFormat源码:

public abstract class DateFormat extends Format {

/*** The {@link Calendar} instance used for calculating the date-time fields* and the instant of time. This field is used for both formatting and* parsing.**

Subclasses should initialize this field to a {@link Calendar}* appropriate for the {@link Locale} associated with this* DateFormat.* @serial*/

protected Calendar calendar;

(2)SimpleDateFormat format方法源码:

private StringBuffer format(Date date, StringBuffer toAppendTo,

FieldDelegate delegate) {

// Convert input date to time field list calendar.setTime(date);

boolean useDateFormatSymbols = useDateFormatSymbols();

for (int i = 0; i < compiledPattern.length; ) {

int tag = compiledPattern[i] >>> 8;

int count = compiledPattern[i++] & 0xff;

if (count == 255) {

count = compiledPattern[i++] << 16;

count |= compiledPattern[i++];

}

switch (tag) {

case TAG_QUOTE_ASCII_CHAR:

toAppendTo.append((char)count);

break;

case TAG_QUOTE_CHARS:

toAppendTo.append(compiledPattern, i, count);

i += count;

break;

default:

subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);

break;

}

}

return toAppendTo;

}

当多个线程同时使用相同的 SimpleDateFormat 对象【如用static修饰的 SimpleDateFormat 】调用format方法时,多个线程会同时调用 calendar.setTime 方法,可能一个线程刚设置好 time 值另外的一个线程马上把设置的 time 值给修改了导致返回的格式化时间可能是错误的。

4.SimpleDateFormat线程安全使用。

(1)使用ThreadLocal处理static方法

public static final ThreadLocal df = new ThreadLocal() {

@Override

protected DateFormat initialValue() {

return new SimpleDateFormat("yyyy-MM-dd");

}

};

System.out.println(df.get().format(new Date()));

(2)使用JDK8 DateTimeFormatter 替代。

==============================================

三、转机

Java的很大一个优势就是社区强大,第三方库非常多,Java自身日期API不好,但有第三方Joda-Time库,JDK7及以前的替代使用。

2005年,Stephen Colebourne创建了Joda-Time库,作为替代的日期和时间API。Stephen向JCP提交了一个规范,他本人作为规范的领导人,该规范就是JSR 310,在Java8中实现并发布。

Java8整合了Joda-Time功能,在java.time包下,非常好用。

1.Java8日期、时间API包介绍java.time包:这是新的Java日期/时间API的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。所有这些类都是不可变的和线程安全的,在绝大多数情况下,这些类能够有效地处理一些公共的需求。

java.time.chrono包:这个包为非ISO的日历系统定义了一些泛化的API,我们可以扩展AbstractChronology类来创建自己的日历系统。

java.time.format包:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为java.time包中相应的类已经提供了格式化和解析的方法。

java.time.temporal包:这个包包含一些时态对象,我们可以用其找出关于日期/时间对象的某个特定日期或时间,比如说,可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法,因为它们都具有“withXXX”的格式。

java.time.zone包:这个包包含支持不同时区以及相关规则的类

2.Java8日期时间API主要类有:

LocalDate:表示不带时间的日期

LocalTime:表示不带日期的时间

LocalDateTime:日期和时间类

ZoneId:时区

ZonedDateTime:一个带时区的完整时间

Instant:Unix 时间,它代表的是时间戳,比如 2018-01-14T02:20:13.592Z

Clock:获取某个时区下当前的瞬时时间,日期或者时间

Duration:表示一个绝对的精确跨度,使用毫秒为单位

Period:这个类表示与 Duration 相同的概念,但是以人们比较熟悉的单位表示,比如年、月、周

DateTimeFormatter:格式化输出

TemporalAdjusters:获得指定日期时间等,如当月的第一天、今年的最后一天等

ChronoUnit:时间单位枚举,用于加减操作

ChronoField:字段枚举,用于设置字段值。

主要类图:

时间范围示意图:

3.美中不足

Java8 日期API经过了Joda-Time长时间的迭代,但也不是完美无瑕。

Java8解析 yyyyMMddHHmmssSSS 问题:

bug地址:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8031085​bugs.java.com

这个问题在Jdk9中修复。

Java8中推荐创建DateTimeFormatter方式:

DateTimeFormatter dtf = new DateTimeFormatterBuilder().appendPattern("yyyyMMddHHmmss").appendValue(ChronoField.MILLI_OF_SECOND, 3).toFormatter();

===============================================

四、xk-time时间工具包

我把Java8 time包下的类源码都学习了一遍,发现设计的非常好,结合网上各类DateUtil功能,开发了一个基于Java8 日期API的库 xk-time,目标是用来替代DateUtil的部分功能。

xk-time中已使用推荐方式创建DateTimeFormatter避免Java8解析 yyyyMMddHHmmssSSS bug。

时间转换,计算,格式化,解析,日历和cron表达式等的工具,使用java8,线程安全,简单易用,多达60几种常用日期格式化模板。

0.为什么要开发这个工具?

(1)java8以前的Date API设计不太好,使用不方便,往往会有线程安全问题。

xk-time工具包,使用java8 api,其中Instant、LocalDate、LocalDateTime、LocalTime、ZonedDateTime等都是线程安全的类,而且增加了更丰富的方法,在此基础上开发相关工具类,线程安全,让使用更方便。

(2)常见的DateUtil,往往将时间转换,计算,格式化,解析等功能都放在同一个类中,导致类功能复杂,方法太多,查找不方便。

xk-time工具包,将上面功能按照时间转换,时间计算,时间格式化解析分成3个工具类:DateTimeConverterUtil,DateTimeCalculatorUtil,DateTimeFormatterUtil,每个类只做一个种功能,方便使用。

(3)为了将与时间紧密相关的节假日、农历、二十四节气、十二星座和日历等功能集中起来开发成工具,方便使用。

1.Maven 坐标

com.github.xkzhangsan

xk-time

1.1.0

2.日期转换工具类 DateTimeConverterUtil

包含Date、LocalDate、LocalDateTime、LocalTime、Instant、ZonedDateTime、YearMonth和Timestamp的互相转换

注意,ZonedDateTime相关的转换,尤其是其他时间转ZonedDateTime,要注意时间和对应时区一致。

详细使用可以查看相关测试代码。

3.日期计算工具类 DateTimeCalculatorUtil

包括:

(1)获取时间属性方法,get* 比如getYear(Date date) 获取年部分,getMonthCnLong(Date date)获取月份中文,getDayOfWeekCn(Date date),获取星期中文。

(2)获取时间加操作方法,plus* 比如plusYears(Date date, long amountToAdd) 当前时间年增加amountToAdd值。

(3)获取时间减操作方法,minus* 比如minusYears(Date date, long amountToSubtract) 当前时间年减少amountToSubtract值。

(4)获取时间修改属性方法,with* 比如withYear(Date date, long newValue) 修改当前时间年值为newValue。

(5)获取比较2个时间方法,between* 比如betweenYears(Date startInclusive, Date endExclusive) 比较2个时间,获取年部分。

(6)其他常用方法,比如isLeapYear(Date date) 判断是否闰年,isWeekend(Date date) 判断是否周末,isExpiry(String yearMonthStr) 是否过期等

(7)时区转换计算方法,transform*,比如transform(ZonedDateTime zonedDateTime, String zoneId)

(8)比较2个时间大小和相等方法,compare*,比如compare(Date date1, Date date2)

(9)获取准确的起始时间方法,start*,end*,比如startTimeOfMonth() 当月起始时间 当月第一天日期+00:00:00,endTimeOfMonth() 当月最后一天日期+23:59:59。

(10)相同月日比较判断方法,isSameMonthDay*,betweenNextSameMonthDay*,nextSameMonthDay*, 比如用于生日,节日等周期性的日期比较判断。

(11)星座计算方法,getConstellation*,比如getConstellationNameCn(String monthDayStr),根据日期计算星座。

(12)计算指定年月或起始时间区间的时间列表,getList, 比如getDateList(int year, int month),计算指定年月的时间列表。

(13)减少时间精度方法,reduceAccuracyTo, 比如reduceAccuracyToDay(Date date),减少时间精度到天,其他补0,返回如,2020-04-23 00:00:00。

(14)获取时间戳方法,getEpoch*, 比如getEpochMilli()获取时间戳,getEpochMilliFormat()获取时间戳格式化字符串(yyyy-MM-dd HH:mm:ss)

详细使用可以查看相关测试代码。

4.日期格式化和解析工具类 DateTimeFormatterUtil

包含常用日期格式如:

yyyy-MM-dd

HH:mm:ss

yyyy-MM-dd HH:mm:ss

yyyy-MM-dd'T'HH:mm:ssZ等等

(1)格式化方法, format*, 比如formatToDateStr(Date date) 格式化,返回日期部分,如:yyyy-MM-dd;

format(Date date, DateTimeFormatter formatter) formatter 可以选择已定义好的formatter比如YYYY_MM_DD_HH_MM_SS_FMT(yyyy-MM-dd HH:mm:ss)格式化日期。

(2)解析方法, parse*, 比如parseDateStrToDate(String text) 解析日期yyyy-MM-dd,返回Date;

parseToDate(String text, DateTimeFormatter formatter) 根据 formatter解析为 Date。

(3)自动解析方法,根据字符串特点自动识别解析,smartParse*,比如smartParseToDate(String text) 自动解析Date。

(4)ISO格式(包含T)自动解析方法,根据字符串特点自动识别解析,parseIso*,比如parseIsoToDate(String text) 自动解析Date。

(5)解析时间戳方法, parseEpochMilli*, 比如parseEpochMilliToDate(String text),解析时间戳为Date,如 1590224790000。

(6)解析Date默认格式,parseDateDefaultStr*,比如parseDateDefaultStrToDate(String text)

解析 EEE MMM dd HH:mm:ss zzz yyyy 比如: Sat May 23 17:06:30 CST 2020 为Date。

(7)自定义时区格式化方法,比如 format(Date date, DateTimeFormatter formatter, String zoneId),根据zoneId格式化Date。

注意:格式化和解析与系统时区不同的时间时,使用自定义时区格式化方法,或可以使用withZone方法重新设置时区,比如:

YYYY_MM_DD_HH_MM_SS_SSS_FMT.withZone(ZoneId.of("Europe/Paris") 。

详细使用可以查看相关测试代码。

5.日历工具类 CalendarUtil

包括:

(1)生成指定时间的日历(包含年、月和日层级关系的日历)方法,generateCalendar* 比如generateCalendar(int year, int month) 生成指定年月的日历。

(2)生成指定时间的日历(包含年、月和日层级关系的日历),包含农历和所有节假日信息方法,generateCalendarWithHoliday*, 比generateCalendarWithHoliday(int year, int month, Map localHolidayMap,Map chineseHolidayMap, Map dateTypeMap)生成指定年月的日历,包含农历和所有节假日信息,可以自定义节假日和工作日等。

详细使用可以查看相关测试代码。

6.农历日期类 LunarDate

包含:

(1)农历日期年月日计算。

(2)农历岁次,生肖属相计算。

(3)二十四节气计算等

注意: 仅支持公历1901-2050年的农历转换。

农历和二十四节气计算的准确依赖于lunarInfo和solarTermInfo基础数据的准确性和精确度。

根据测试结果,发现本程序和常用万年历基本一致,常用万年历软件中也有很少量计算误差,欢迎提出问题,会不断进行改进和修正。

详细使用可以查看相关测试代码。

7.节假日计算类 Holiday

包含:

(1)公历节假日计算, getLocalHoliday* 比如getLocalHoliday(Date date) 计算date的公历节日,getLocalHoliday(Date date, Map localHolidayMap) 可以传入自定义公历节日数据。

(2)农历节假日计算, getChineseHoliday* 比如getChineseHoliday(Date date) 计算date的农历节日,getChineseHoliday(Date date, Map chineseHolidayMap) 可以传入自定义农历节日数据。

(3)二十四节气计算, getSolarTerm* 比如getSolarTerm(Date date) 计算date的二十四节气。

注意: 农历和二十四节气使用农历日期类 LunarDate,仅支持公历1901-2050年的计算。

详细使用可以查看相关测试代码。

8.Cron表达式工具类 CronExpressionUtil

cron表达式从左到右(用空格隔开):秒(0-59) 分(0-59) 小时(0-23) 日期(1-31) 月份(1-12的整数或者 JAN-DEC) 星期(1-7的整数或者 SUN-SAT (1=SUN)) 年份(可选,1970-2099)

所有字段均可使用特殊字符:, - * / 分别是枚举,范围,任意,间隔

日期另外可使用:? L W 分别是任意,最后,有效工作日(周一到周五)

星期另外可使用:? L # 分别是任意,最后,每个月第几个星期几

常用cron表达式:

(1)0 0 2 1 * ? * 表示在每月的1日的凌晨2点触发

(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业

(3)0 15 10 ? * 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作

(4)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时

(5)0 15 10 L * ? 每月最后一日的上午10:15触发

(6)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发

包含

(1)验证和格式化Cron表达式方法,isValidExpression和formatExpression。

(2)生成下一个或多个执行时间方法,getNextTime和getNextTimeList。

(3)生成下一个或多个执行时间的日期格式化(yyyy-MM-dd HH:mm:ss)方法,getNextTimeStr和getNextTimeStrList。

(4)对比Cron表达式下一个执行时间是否与指定date相等方法,isSatisfiedBy。

注意: 底层使用 quartz的CronExpression处理。

详细使用可以查看相关测试代码。

源码地址:https://github.com/xkzhangsan/xk-time​github.comhttps://gitee.com/xkzhangsan/xk-time​gitee.com

欢迎 star 和 提建议。

更多Java 日期API应用可以查看我的博客和专栏,欢迎交流学习。xkzhangsanx - 博客园​www.cnblogs.com时间管理,Java日期时间API应用​zhuanlan.zhihu.com68346032805b04f621d5ef0011e0acb7.png

参考:《阿里巴巴Java开发手册》

《Java8实战》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值