今天偶然遇到一个编译时常量(Compile-time Constant)和运行时常量(Run-time Constant)的题目,所以希望通过写一篇博文, 来仔细记录下所有的区别和优劣.
因水平有限, 大部分内容都是查阅资料和其他人的博客来的.
问题起源
首先, 源于在博客https://blog.csdn.net/hzw19920329/article/details/51055736 看到了这个代码
public class GetClass {
public static void main(String[] args) {
System.out.println(Test.name);//①
System.out.println(Test.score);//②
System.out.println(Test.age);//③
}
}
class Test
{
public static int age = 23;
public static final String name = "shanxi";
public static final Integer score = 85;
static
{
System.out.println("Test static block");
}
}
单独运行①输出的结果是: shanxi
单独运行②输出的结果是:
Test static block
85
单独运行③输出的结果是:
Test static block
23
于是开始思考,为什么会这样,原博主也解释了说是"可以看出虽然两者都是static final 类型的,但是name作为"编译常量",他是不需要对类Test进行初始化就可以读取,因而不会执行Test中的静态代码块;但是score虽然作为static final变量,但是他并不是"编译常量",需要初始化Test类之后才可以,因而会首先执行static代码块,随后输出score的值;对于非final的static域,那么对他访问之前要先进行链接(为这个域初始化空间)和初始化(初始化该存储空间),也就明白了单独运行③会首先输出static块的值原因;"
开始解决
第一步, 什么是编译常量
Oracle的官方文档 15.28 Constant Expressions 详细的解释了,什么是 Compile-time Constant
1. 原始类型字面量,或者String字面量 2. 能转型为原始类型字面量,或String字面量的常量 3. 一元运算符(+,-,~,!,但不包含++, --) 和1,2组成的表达式 4. 多元运算符(*,/和%)和1,2组成的表达式 5. 附加运算符( additive operators) (+ 或 -)与之前几条组成的表达式 6. 位移运算符(<<,>>, >>>)和之前几条组成的表达式 7. 关系运算符(<,<=,>,>= ,不包括 instanceof)与之前几条组成的表达式 8. 关系运算符(==,!=)与之前几条组成的表达式 9. 位运算符(&, ^, |)与之前几条组成的表达式 10. 条件与和条件或运算符(&&, ||) 与之前几条组成的表达式 11. 三元运算符 (?:)和之前几条组成的表达式 12. 带括号的表达式,括号内也是常量表达式 13. 引用常量变量的简单变量 14. 类中的常量变量引用,使用类的全限定名或类名进行引用(String.class)
针对其简单解释其中几个
1, 编译时常量必须定义为基本类型或者String,
基本类型指的是int, long, short, boolean, char, float, double
所以, 如果Test类中如果再定义一个public static final int b=2; 那么Test.b输出时,一样不会加载Test类, 而是直接输出2.不会输出静态代码块.
2, 编译时常量也可以是基本类型加上部分运算符, 上面的规则我基本都试过了, 都是对的. 其实只要记住, 运算符只要不是instanceof和自加, 自减运算符就基本都可以. 这样在笔试的时候, 可以记得更清楚.
3.其实上面介绍中少了最重要的一项, 变量必须是用final修饰的, 而且变量必须在声明的同时进行赋值(其实这是final修饰词的要求)
编译时常量是否一定需要static修饰?
这个问题值得深究, 分两方面来解释这个问题
- 如果是要外部调用, 那没有static是不行的, 这个很好解释, 如果没有static, 那么想要得到这个常量,必须得new出来对象才行, 所以外部调用时, 必须要用static修饰
- 内部使用, 内部使用时, static为非必须存在的.
可能的面试题: 编译时常量存在什么样的风险?
(CSDN中别人这么说的)
编译时常量在编译的时候会被直接写成对应的值, 而不会再从原来的类中读取, 这样就会导致问题的产生:
如果A类定义了常量, B类使用了常量, 并且都进行了编译, 当A类的源码被改动了, 常量的值发生了变化, 我们对A类进行重新编译, 但是没有对B进行重新编译, 那么B类中用到的是原来A类中的常量值, 即旧值, 这样就导致了风险的产生.
但是理论上是这样的, 实际上操作中IDE会自动帮我们重新编译B类, 所以我试着操作了几次, 并没有发生这样的风险,
但是这个风险的确是存在的.