public class ConstantFolding {
static final int number1 = 5;
static final int number2 = 6;
static int number3 = 5;
static int number4= 6;
public static void main(String[ ] args) {
int product1 = number1 * number2; //line A
int product2 = number3 * number4; //line B
}
}
一个经典例题,代码中的lineA和lineB有何不同?
答案是: lineA的语句在生成字节码后,再反编译,得到的结果是
int product1 = 30;
而lineB在生成字节码并反编译后:
int product2 = number3 * number4;
原因是 lineA发生了“常量折叠”。 常量折叠:Java编译器在编译时,会把不可更改的常量自己计算出来。
number1、number2 都是final类型的,所以编译器“知道”他是不会改变的,便提前计算出他的值。
所以,常量折叠是发生在编译时的。
这里的编译时,指的是将Java源代码转换成字节码的过程,是不发生在jvm当中的,进行的是语法规则检查。而运行时,则发生在将字节码装载到JVM的过程中。
发生在编译时的过程包括:
1、方法重载:
方法重载又叫编译时多态,因为重载的方法的区别在于参数的不同,编译器是可以根据调用该方法时传入的参数来判断具体调用哪个方法的,也就可以把对应方法的字节码写入.class文件。
2、泛型:
编译器负责检查程序中类型的正确性,然后把使用了泛型的代码翻译或者重写成可以执行在当前JVM上的非泛型代码。(泛型擦除)
发生在运行时的过程:
1、方法覆盖
方法覆盖又叫运行时多态,因为编译器在编译时不知道究竟是调用父类的方法还是子类的方法,所以只有在装入JVM时才会由JVM做出决定。
这里一个引申:
Person p = new Man();
Man继承了Person类,而在编译时,由于还不涉及内存,所以在编译时,只产生了类型为Person类型的引用,在运行时,才会有产生Man的对象。
另外有个结论是:对象调用编译时类型的属性和运行时类型的方法。
(理解参见)
还有一些过程既在运行时发生,也在编译时发生:
1、注解:
有些注解是发生在编译期检查的,而有些是运行时。
2、异常
异常包括运行时异常(不受检查的异常)和编译时异常。 《Thinking in Java》 P263
运行时异常,也称作未检测的异常,这表示这种异常不需要编译器来检测。
RuntimeException是所有可以在运行时抛出的异常的父类。一个方法除要捕获异常外,如果它执行的时候可能会抛出
RuntimeException的子类,那么它就不需要用throw语句来声明抛出的异常。 例如:NullPointerException,ArrayIndexOutOfBoundsException,等等 。
受检查异常都是编译器在编译时进行校验的,通过throws语句或者try{}cathch{} 语句块来处理检测异常。编译器会分析哪些异常会在执行一个方法或者构造函数的时候抛出。