编译时常量和运行时常量分析

今天偶然遇到一个编译时常量(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修饰?

这个问题值得深究, 分两方面来解释这个问题

  1. 如果是要外部调用, 那没有static是不行的, 这个很好解释, 如果没有static, 那么想要得到这个常量,必须得new出来对象才行, 所以外部调用时, 必须要用static修饰
  2. 内部使用, 内部使用时, static为非必须存在的. 

可能的面试题: 编译时常量存在什么样的风险?

(CSDN中别人这么说的)

编译时常量在编译的时候会被直接写成对应的值, 而不会再从原来的类中读取, 这样就会导致问题的产生:

如果A类定义了常量, B类使用了常量, 并且都进行了编译, 当A类的源码被改动了, 常量的值发生了变化, 我们对A类进行重新编译, 但是没有对B进行重新编译, 那么B类中用到的是原来A类中的常量值, 即旧值, 这样就导致了风险的产生.  

但是理论上是这样的, 实际上操作中IDE会自动帮我们重新编译B类, 所以我试着操作了几次, 并没有发生这样的风险, 

但是这个风险的确是存在的.

猜你喜欢

转载自www.cnblogs.com/JoeySong/p/9102188.html