我们需要区分常量的两个方面:
- 为开发时已知的值命名,为更好的可维护性引入了这些值,以及
- 编译器可用的值。
还有第三种相关的变量:其值不变的变量,即值的名称。这些不可变变量和常量之间的区别是,在确定/分配/初始化值时:在运行时初始化变量,但是在开发过程中知道常量的值。这种区别有点混乱,因为在开发过程中可能知道一个值,但实际上仅在初始化期间创建了一个值。
但是,如果常量的值在编译时已知,则编译器可以使用该值执行计算。例如,Java语言具有常量表达式的概念。常量表达式是仅由基元或字符串的文字,对常量表达式的操作(例如强制转换,加法,字符串连接)和常量变量组成的任何表达式。[ JLS§15.28 ]常量变量是final
使用常量表达式初始化的变量。[JLS§4.12.4]因此对于Java,这是一个编译时常量:
public static final int X = 7;
当在多个编译单元中使用常量变量,然后更改声明时,这变得很有趣。考虑:
现在,当我们编译这些文件时,B.class
字节码将声明一个字段,Y = 9
因为它B.Y
是一个常量变量。
但是,当我们将A.X
变量更改为其他值(例如X = 0
)并仅重新编译A.java
文件时,B.Y
仍然会引用旧值。此状态A.X = 0, B.Y = 9
与源代码中的声明不一致。调试愉快!
这并不意味着不应该更改常量。常量绝对比在源代码中没有任何解释的幻数更好。但是,公共常量的值是公共API的一部分。这不是特定于Java,而是在C ++和其他具有单独编译单元的语言中发生。如果更改这些值,则需要重新编译所有相关代码,即执行干净编译。
根据常量的性质,它们可能导致开发人员的错误假设。如果更改了这些值,则可能会触发错误。例如,可以选择一组常数,以便它们形成某些位模式,例如public static final int R = 4, W = 2, X = 1
。如果将这些更改为类似的结构,R = 0, W = 1, X = 2
则现有的代码(例如)将boolean canRead = perms & R
变得不正确。只是想想随之而来的乐趣就是Integer.MAX_VALUE
改变!这里没有解决方法,重要的是要记住一些常量的值确实很重要,不能简单地更改。
但是对于大多数常量而言,只要考虑上述限制,就可以更改它们。当含义而不是特定值很重要时,可以安全地更改常量。例如,诸如BORDER_WIDTH = 2
或TIMEOUT = 60; // seconds
或之类的模板就是这种情况,API_ENDPOINT = "https://api.example.com/v2/"
尽管可以说其中的一些或全部应该在配置文件中指定,而不是在代码中指定。