第一:求不要复制粘贴,有毛意思,现在国内博客,一搜全是大片大片一样的,粘贴党自己不觉得恶心吗?都这样,国内的博客还怎么用?有一些人是出于怕自己以后找不到,所以干脆拷到自己的某个地方去,事后你真的看了吗?推荐一个浏览器的好功能,叫页面收藏管理,推荐一个好插件,叫google keep。
第二:水平有限,一定会有想不到,考虑不周,或着理解有误的地方。跪求指点,指正。
经历了好多个项目之后,总是会被时间处理搞到恶心。之前Java的时间处理真的不好用,不过也不能怪谁,毕竟有些业务对时间的处理,真的很奇葩,形形色色,千奇百怪,很难去抽象一个工具,能应付主流的需求。java8出来了一个单独的包,用于时间的处理,一直也没时间特意去研究,总是用的时候谷歌一下,拿来主义,没有深入过。最近难得压力不大,趁机学习整理一下。 大概的过程,是先分后总。先一个个的类去分析,适当时候做对比分析,和相关结构设计分析。最后会有一个设计和常见用法的总结。
看源码不要怕,好的项目,代码写的总让人乍一看有点发蒙,坚持看下去,一路都是惊叹。而且遵守各种开发规范,融合了常用的设计模式,面向对象的思想,原则,心中把握着某个小点的大结构,就不容易偏离主线。更重要的是,像JDK源码的注释非常详细,而且类的分工极其明确,虽然某个类看上去有n多的方法,但它主要可能就干了一件事。
java.time.Duration
感悟:有时候重复造轮子的原因,只是因为自己学的太少,知道的太少。甚至不知道已有这个轮子了。
- 枚举类也可以继承接口。
虽然之前知道看过枚举类字节码反编译后的代码,知道枚举类只是一种编译上的语法糖,但一直在内心还是将它和普通类区别对待,以至于从来没有考虑过枚举类还可以实现接口。 - 类的循环依赖,并不是一定不可行。
Duration实现了TempralAmount接口,接口中有个getUnits()方法,返回的是Duration中使用的两种时间Unit(Second和Nanosecond),而枚举类ChronoUnit定义了常见的时间Unit。问题在于ChronoUnit中对各种Unit定义的方法依赖了Duration。这就造成了如果Duration想复用ChronoUnit关于Second和Nanosecond两个Unit,就会造成循环依赖。
解决方案: 代码截图来自jdk1.8 java.time.Duration - 如果不希望某个作为实参的list内元素被修改,可以考虑复制一个跟list一模一样的新list作为实参传入方法,也可以考虑像Collections.unmodifiableList()一样,自己实现一个List的实现类,不用深度拷贝list内所有的元素,只是简单的限制对list元素的修改操作。(当然,有这个轮子我们就不用再重复造轮子了,这里只是觉得这个思想可以借鉴) 代码截图来自jdk1.8 java.util.Collections
- 有时间多看看JDK源码,有些写法确实是太经典了。比如Math类,很多位运算太经典了。一个看了足够多技术书籍,资料,官网文档,源码的工程师,就像一个拥有上百种工具的现代化猎人,相比较于刀耕火种时代的猎人来说,效率高太多了。虽然自己也知道什么叫位运算,但真写起来,就用不好了;也知道一些并发知识,看了concurrent包的源码后,觉得自己还是考虑的太少,想的太简单;也了解一些分布式一致性原理,看了zk的实现,才知道从理论到实现差太多了。
- 某个类,如果出现常见的本类实例,就该把这个实例在本类中声明定义出来,再看能不能依赖这个实例做更多的事情。比如Duration的很多操作,都是基于Duration.ZERO实例进行的。
问题:
- 精彩~!document中说Duration实例,比如duration,的某些方法返回的是一个副本,原来的duration实例不受影响。这个给我们的第一感觉是,在调用过程中应该用一个新的变量,比如newDuration,来接收这个方法的返回值。并且我操作newDuration应该不会对原实例duration造成影响。但是比如plus*系列实例方法,在改动因素为0的情况下,返回的是本实例duration,而并不会新创建一个副本。可能是考虑内存消耗的问题,改动因素为0,实际上newDuration和duration的值是相同的,所以只保留了一个。但文档的说法,和方法对内存消耗方面的优化,就有了冲突。
虽然文档的说法是有问题的,但如果我们按照文档的说法去操作,是否就会有问题呢?当改动因素为0的时候,我去操作newDuration,就会影响到duration?并不会,这就是它精彩的地方,Duration对外暴露的涉及改动的全部实例方法,返回的都是一个备份(除非改动因素为0),就不可能发生上述的问题。
代码截图来自jdk1.8 java.time.Duration
小结:
java.time.Duration以秒和纳秒两个部分的组合代表了一个时间间隔。
只有一个私有的构造方法Duration(long, long)。
静态方法一堆,但都是用来以各种方式,获取Duration的实例。如下图:
上图的几点说明:
- Temporal的具体说明后面会有,between(Temporal, Temporal)可以看成是两个时间点之间的时间间隔
- TemporalAmount后面也有,from(TemporalAmount)就是将一堆年月日,时分秒,毫秒,纳秒统一换算成秒和纳秒的形式,加起来,得到一个时间间隔
- parse(CharSequence)这个方法感觉不那么友善,因为String的格式要求是:
The formats accepted are based on the ISO-8601 duration format PnDTnHnMn.nS with days considered to be exactly 24 hours.
关于PnDTnHnMn.nS
的解释如下: 意思是String总要以P开头,如果出现时分秒,则前面要有个字母T,而且整体到部分,都有正负号一说。
为什么要简单说明一下呢?因为Duration重写了toString方法,我看源码有个习惯,总喜欢去打印一下一些结果试试,结果Duration的实例,打印出来的结果,就是这么个格式。不同点在于,toString打印出来的没有D(日)的部分,对应的日数,会换算成H(时)。即:P2DT3S(2天3秒),toString的结果是PT48H3S.
实例方法又一堆,大体分这么几类
- 第一类,加减乘除:典型的是
minus*
系列和plus*
系列 这一类方法,是通过指定的时间间隔来修改原有的时间间隔。
涉及duration修改的方法还有,
- 指定一个duration的秒或纳秒数,从而获得一个新的duration
- 取绝对值
- 取反
- 第二类,获取duration的值。典型的
get*
的直接指定获取和to*
的转换后结果 几点说明:
- getUnits()返回的是Duration包含的时间单元。一开始就说了,Duration是依赖秒和纳秒来表示时间间隔的。所以这里返回的是一个列表,列表包含秒和纳秒两个时间单元。
- toString()也放这儿了,关于它的说明,上文有提到过。
- 不知道你们有没有注意到,
to*
系列中有日时分,毫秒纳秒,唯独没有toSeconds()?其实是有的,只是是private的私有方法,它的返回类型也不同,是一个BigDecimal,用于将duration实例转换成一个小数,由秒构成整数部分,纳秒构成小数部分。
- 第三类,比较判断型 compareTo()区分正负号,跟小数中的比较规则相同。
- 第四类,不推荐使用的,将自己从某个时间点减去,或把自己加到某个时间点上。这两句话听着就别扭,主宾关系表述的跟我们正常的思维都不一致。正常的表述应该是,从某个时间点,减去或加上某个时间间隔。诶,没错,用于表示时间点的Temporal就有与Duration在这里一一对应的实例方法。所以Duration的这两个方法就不推荐使用了。
Duration就先到这儿了。