常量大家都不陌生,但如果你脑袋里一听到这个词,就只能想得起来”常量不可修改“的话,那你就得好好往下读一读了。
今天写这篇博客不得不感叹一句,知识真的是得来回嚼来回嚼才消化得了。
为什么突然感叹这个呢?
因为我昨天在看Spring Boot,看了对底层有点懵逼,因为Spring没学扎实,回头复习了下Spring,复习Spring又发现对动态代理模式掌握得不好,动态代理看了又去复习反射,反射看了发现自己对JVM又产生了些疑惑,一下又回到了常量这上边。
经过了自顶向下复习,这会又自底向上推回,知识还是学扎实的好,不多说了,哈哈。
在Java程序里,常量用关键字static final修饰,常量又分为:
下面我们就分开来看看,举一些好理解的例子,直观的实验。
下面是一个编译期常量:
static final int A = 1024;
编译时,所有A的引用都将被替换成字面量(即1024),类型必须是基本类型或String。
下面就是一个运行时常量:
static final int len = "Rhine".length();
运行时才能确定它的值。
要是你能理解以下内容应该能给你带来一点收获!当然如果你是还没有学习到关于JVM相关知识的同学,暂时不用深究这一点了。
什么叫对类的依赖性?单从字面上理解就是需不需要类,其实也就是与类的创建有没有关系。
那么类的创建和我常量有什么关系吗?或则更具体来说,编译期常量和运行时常量对类的创建有什么不同的影响?
要解答以上的疑问还得先来看类在什么情况下会创建:
JVM的虚拟机规范严格规定了有且只有5种情况必须立即对类进行“初始化”,其中第一条就是:
遇到new、getstatic或invokestatic这4条字节码指令时,如果类没有初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:
(1)使用new关键字实例化对象时
(2)读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)时
(3)调用一个类的静态方法时
以上内容摘抄自《深入理解Java虚拟机》7.2节 类加载的时机
对常量池有疑惑的同学可以参考下这篇文章。
其中场景(1)和(3)可以不用说了,常量这部分内容正是涉及到了场景(2),我们来做个很简单的程序来看看。
class Test { //静态代码块 static { System.out.println("Class Test Was Loaded!"); } //编译期常量 public static final int num = 10; //运行时常量 public static final int len = "Rhien".length(); } public class Main { public static void main(String[] args) throws Exception { System.out.println("num:"+Test.num); System.out.println("=== after get num ==="); System.out.println("len:"+Test.len); } } /* 打印输出: * num:10 * === after get num === * Class Test Was Loaded! * len:5 */
代码结构很简单,一旦Test类被初始化,那么就会被立即执行。
根据程序运行结果,我们就可以得出结论了:编译期常量不依赖类,不会引起类的初始化;而运行时常量依赖类,会引起类的初始化。
所以我们再重新捋一捋刚才场景(2)那段话,大致可以理解为:“读取或设置一个类的静态字段(编译期常量除外)时”。
由于编译时,常量会被替换为字面量,这是JVM提高运行效率优化代码的一种方式,但有时候也会带来一定的麻烦。
如果我们项目超大,项目整个编译一次特别耗费时间,那么我们有可能会只编译代码修改的部分。而一旦我们修改了常量A,但又未重新编译所有引用A常量的部分(即.java文件),那么就会导致未重新编译的那部分代码继续使用A的旧值。
下面写个非常简单的实验看看。
定义常量的Book类:
public class Book { //编译期常量,书本价格10元 public static final int price = 10; }
定义Student类和mian方法:
public class Student { public int cost = Book.price; public void printCost() { System.out.println("书费"+this.cost+"元"); } public static void main(String[] args) { Student stu = new Student(); stu.printCost(); } } /* 打印输出: * 书费10元 */
【Java文件】Book.java、Student.java文件
【字节码文件】Book.class、Student.class文件
运行结果为:书费10元
现在修改Book.java文件中的price=5,并使用javac命令仅仅只重新编译Book.java文件:
javac Book.java
执行java命令,再次运行main方法:
java Student
观察结果:结果与第一次相同,书费10元