前因:在学习Calendar类的相关操作时发现,可以通过该类的set()方法设置日期时间的某一部分,查看源码发现,相应的日期时间变量全都是static final类型变量,即静态常量,觉得很不可思议。
下面写写关于我查阅资料后的一些理解,希望对看官们有所帮助:
- 先上API中关于Calendar属性变量的定义:
可以发现很多字段都被static修饰,点开其中一个进行查看,如下:
从截图可以发现,所有的关于日期/时间的属性都设置为了static final,即常量,那么为什么我们可以通过set()对Calendar的日期时间进行设置呢?
- 于是,这一次我们需要查看的是set()方法的源码:
部分源码如下:
/**
* 这里是方法说明
* Sets the given calendar field to the given value. The value is not
* interpreted by this method regardless of the leniency mode.
*
* @param field the given calendar field.
* @param value the value to be set for the given calendar field.
* @throws ArrayIndexOutOfBoundsException if the specified field is out of range
* (<code>field < 0 || field >= FIELD_COUNT</code>).
* in non-lenient mode.
* @see #set(int,int,int)
* @see #set(int,int,int,int,int)
* @see #set(int,int,int,int,int,int)
* @see #get(int)
*/
public void set(int field, int value)
{
// If the fields are partially normalized, calculate all the
// fields before changing any fields.
if (areFieldsSet && !areAllFieldsSet) {
computeFields();
}
internalSet(field, value); //!!!重点看这里!!!
isTimeSet = false;
areFieldsSet = false;
isSet[field] = true;
stamp[field] = nextStamp++;
if (nextStamp == Integer.MAX_VALUE) {
adjustStamp();
}
}
源码比较复杂,涉及的底层有很多,我们这里只看最核心的一句,就是internalSet(field,value),很明显是调用了其他的方法进行修改值,所以下一步,我们查看internalSet(field,value)方法的源码:
/**
* Sets the value of the given calendar field. This method does
* not affect any setting state of the field in this
* <code>Calendar</code> instance.
*
* @throws IndexOutOfBoundsException if the specified field is out of range
* (<code>field < 0 || field >= FIELD_COUNT</code>).
* @see #areFieldsSet
* @see #isTimeSet
* @see #areAllFieldsSet
* @see #set(int,int)
*/
final void internalSet(int field, int value)
{
fields[field] = value;
}
从这个方法的源码我们可以看出,我们一开始调用set(field,value)时,第一个参数是作为fields数组的索引来用,第二个参数才是我们的目标值,那么这个fields数组到底是何方神圣呢?上源码:
/**
* The calendar field values for the currently set time for this calendar.
* This is an array of <code>FIELD_COUNT</code> integers, with index values
* <code>ERA</code> through <code>DST_OFFSET</code>.
* @serial
*/
@SuppressWarnings("ProtectedField")
protected int fields[];
从源码的文档说明来看,fields保存的就是当前设置时间的日历字段值。
什么意思?很简单,下面划重点!!!
意思就是说,fields存储了我们这个日历的真实内容,包括日期和时间,是如何保存的呢?是将我们的日期时间进行分段保存在数组里,如:
YEAR MONTH DAY_OF_MONTH …
1 2 5 …
通过将我们的日历进行分段,年为一个段,月为一个段……,最终一个段保存在数组的一个位置上。这就是fields存储日历的原理。
那么接下来解释为什么我们可以通过set()来修改日历值:
- 先想第一个问题:我们修改日历的某一部分,是不是实际上是修改fileds数组上某个元素的值,而通过源码我们知道,fileds数组是没有被final修饰的,因此是可以修改的。
- 那么第二个问题:我们是如何定位到fileds数组的某个具体位置上进行值的修改的呢?
其实就是通过set(参数1,参数2)中的第1个参数。
假设“年”存储在数组的第一个元素上,那么一般而言,我们修改的时候,应该是这样输入:set(0,2021),其中0表示要修改的数组元素的位置,2021表示具体想修改成的值。
但对我们开发者而言,要记住日历的每个段在数组上的索引并不容易,因此API在提供时,对索引进行了命名,也就是利用了静态常量,如static final YEAR = 0;这种方式,从而达到见名知义的效果,方便开发者调用API,所以才会出现了那么多的静态常量属性。
好了,综上所述呢,我们的静态常量其实并不是我们想修改的变量,我们是将静态常量作为数组的索引,由于静态常量不会被改变,所以数组上的固定位置不会被改变,当我们想修改某个日历中的段时,通过静态常量去定位到实际保存日历段的数组fields的某个具体位置,对元素值进行修改,从而达到我们的目的。这也就是为什么set()需要两个参数,而get()可以通过指定参数得到我们需要的数据的原因,底层都是通过静态常量对fileds数组进行操作的!
核心结论:日历存储时,利用了key-value方式,其中key对应了Calendar中的静态常量,不可被修改;value对应了fileds[ ]中某个具体位置上的值,可以被修改,通过key与某个具体位置进行关联。
以上内容纯属个人理解,如有错误,还望海涵!