一、背景引入
近期在开发一个项目的后台时,使用到了邮件发送的服务。由于考虑变化的可能性不大,将邮箱地址和发信服务器使用final static修饰符定义在一个util类中,并在发信代码中调用(而没有做成配置文件,其实后来想想,还是需要做成配置文件会更稳妥些),后面升级到服务器时发现邮箱地址需要改动,于是修改了util类中的变量内容,并将编译之后的util类的class文件覆盖到线上,结果发信地址仍然没有变化…(坑爹啊!)
组里的学长机智地将发信的程序的class文件抓取下来,本地反编译,竟然发现了:邮箱地址和发信服务器等使用final修饰的数据,在外部被引用的地方,被Java编译器直接编译成了常量,故单纯替代util类无法修正错误,后面对引用和工具类进行一并更新才解决了这个问题。
二、实操下
1、final+static
public class Link {
public final static String linkStr = "STRING1";
}
public class MainClass {
public static void main(String[] args) {
String localStr = Link.linkStr;
System.out.println(localStr);
}
}
程序运行正常,打开编译后的class文件并反编译,可以看到:
import java.io.PrintStream;
public class MainClass
{
public MainClass()
{
}
public static void main(String args[])
{
String localStr = "STRING1";
System.out.println(localStr);
}
}
public class Link {
public final String linkStr = "STRING1";
}
public class MainClass {
public static void main(String[] args) {
Link link = new Link();
String localStr = link.linkStr;
System.out.println(localStr);
}
}
程序运行正常,打开编译后的class文件并反编译,可以看到:
import java.io.PrintStream;
public class MainClass
{
public MainClass()
{
}
public static void main(String args[])
{
Link link = new Link();
link.getClass();
String localStr = "STRING1";
System.out.println(localStr);
}
}
3、final+static+运行时初始化
public class Link {
public final static String linkStr;// = "STRING1";
static{
linkStr = "STRING1";
}
}
public class MainClass {
public static void main(String[] args) {
String localStr = Link.linkStr;
System.out.println(localStr);
}
}
打开编译后的class文件:
import java.io.PrintStream;
public class MainClass
{
public MainClass()
{
}
public static void main(String args[])
{
String localStr = Link.linkStr;
System.out.println(localStr);
}
}
这个不难理解,因为在编译的阶段,Link的linkStr变量还没有初始化,尽管它是final修饰的。此时如果注释掉static代码块的初始化逻辑,编译器将报错,因为编译器会保证final修饰的变量必须进行初始化。
三、设计的动机
在网上查看了一些博客,没有较为切确的解释,我认为这是Java一个优化设计:即把运行时的开销提前到编译时支出,从而提高程序的运行性能。此外,对于final修饰的方法,网上有解释如下:
当一个方法被修饰为final方法时,意味着编译器可能将该方法用内联(inline)方式载入,所谓内联方式,是指编译器不用像平常调用函数那样的方式来调用方法,而是直接将方法内的代码通过一定的修改后copy到原代码中(将方法主体直接插入到调用处,而不是进行方法调用)。这样可以让代码执行的更快(因为省略了调用函数的开销)
另一方面,私有方法也被编译器隐式修饰为final,这意味着private final void f()和private void f()并无区别。