在Java中可以使用一个java文件来保存常量,因此存在两种方式:
1.在一个普通类当中定义,则每个都要加上 public static final 这三个修饰符
2.直接在接口中定义,则每一个修饰符都不需要(接口默认为公开静态常量)
使用方法都是类名.属性名的方式直接静态引用
然而很多网上说两者区别时都提到一点在class中定义常量支持动态编译,即修改常量后,只需要替换这一个常量文件,即可实现修改的变化。有的人给出的解释是在编译过程中,interface的值会被直接编译到引用的类当中,因为它是确定的不可改变的值,而class只是将这个值的引用编译到引用的类当中,因此它的值在运行中是可能改变。
我认为上述解释存在偏差:
根据常量的定义java定义为不可改变值的变量,它被final修饰,因此无论是class中定义还是interface中定义,决定一个变量是否为常量的关键因素不是在哪里定义而在于它是否被final所修饰。因此在编译过程中,引用类会将被final修饰的内容直接嵌入到当前位置,而不论是interface还是class。这才应该是合乎逻辑的解释。而如果要实现上面所说的动态编译,那么我们不能直接通过类名.属性名的方式直接获取常量,而必须提供额外的获取方法来获取这个常量,即按照java通常属性私有化,方法公开化进行处理。
验证过程如下:
jdk版本:
有一个常量文件A.java类内容如下
public class A{
public static final String a = "环境测试";
}
有一个测试类B.java,内容如下:
public class B{
public static void main(String[] args){
System.out.println(A.a);
}
}
只编译A文件,则只有A产生class文件
只编译B文件,则AB都会产生class文件
因为B引用了A,所以A编译了。
运行B 得到正确结果
反编译B
下一步修改A内容,编译A文件,切记不可编译B文件
修改内容如下:
public class A{
public static final String a = "测试改变";
}
编译后运行B文件结果
然后他们并没有发生如网上所说的动态编译
反编译B文件:
其实可以发现A文件的常量是写进了B文件的代码中的,所以它并没有改变。为什么这么说?我们将这个常量引用方式改一下,以静态方法的方式引用
public class A{
public static final String a = "测试改变";
public static final String getA(){
return a;
}
}
public class B{
public static void main(String[] args){
System.out.println(A.getA());
}
}
编译AB文件后运行结果如下:
下面修改常量a的值,只编译A文件运行
public class A{
public static final String a = "我再一次变身了";
public static final String getA(){
return a;
}
}
运行B文件
值发生了改变,此时反编译B文件
此刻的位置没有出现显示的内容。
为了证明常量的特殊性,将final去掉,
public class A{
public static String a = "我再一次变身了";
}
进行编译AB文件运行如下
现在修改A文件如下:
public class A{
public static String a = "我正在测试变化";
}
运行结果如下
反编译B文件
观察上述红线框内的内容发现:常量在编译的时候是被写进代码中的,即便后来替换了常量池的内容也不会改变已经编译好的文件,因此编译文件时必须要编译与该常量有关的所有文件才能保证该常量被正确编译到了代码中。并不是像网上所说的只需要替换常量池文件即可。因为无论是在class文件中定义还是在interface中定义,他们都被final修饰了,都是不可改变的量,也自然而然的被直接编译到目标文件中了,而不会去常量池获取值,除非你使用方法去获取。这是合乎常理的。
附上interface的运行结果
初始运行:
改变A编译A:
反编译B
编译B运行,反编译B
可以按照Class与Interface的关系来解释:Interface是只含有公开抽象方法和公开静态常量的抽象类,抽象类是被abstract修饰的可能包含了抽象方法的Class。因此根据其定义,Interface是Class的一小部分本质上都是Class。
因此结论是:变量的面值是否被编译到代码中不在于变量所处的位置,而在于变量是否被final修饰。一个被final修饰的变量一旦被直接引用,那么他的面值将会被编译到代码中。