final关键字与Java编译器的小坑
2016-04-05 14:59
302 查看
一、背景引入
近期在开发一个项目的后台时,使用到了邮件发送的服务。由于考虑变化的可能性不大,将邮箱地址和发信服务器使用final static修饰符定义在一个util类中,并在发信代码中调用(而没有做成配置文件,其实后来想想,还是需要做成配置文件会更稳妥些),后面升级到服务器时发现邮箱地址需要改动,于是修改了util类中的变量内容,并将编译之后的util类的class文件覆盖到线上,结果发信地址仍然没有变化…(坑爹啊!)
组里的学长机智地将发信的程序的class文件抓取下来,本地反编译,竟然发现了:邮箱地址和发信服务器等使用final修饰的数据,在外部被引用的地方,被Java编译器直接编译成了常量,故单纯替代util类无法修正错误,后面对引用和工具类进行一并更新才解决了这个问题。
二、实操下
1、final+static
程序运行正常,打开编译后的class文件并反编译,可以看到:
2、final修饰
程序运行正常,打开编译后的class文件并反编译,可以看到:
可见,static与否并不会影响Java的编译。
3、final+static+运行时初始化
打开编译后的class文件:
这个不难理解,因为在编译的阶段,Link的linkStr变量还没有初始化,尽管它是final修饰的。此时如果注释掉static代码块的初始化逻辑,编译器将报错,因为编译器会保证final修饰的变量必须进行初始化。
三、设计的动机
在网上查看了一些博客,没有较为切确的解释,我认为这是Java一个优化设计:即把运行时的开销提前到编译时支出,从而提高程序的运行性能。此外,对于final修饰的方法,网上有解释如下:
当一个方法被修饰为final方法时,意味着编译器可能将该方法用内联(inline)方式载入,所谓内联方式,是指编译器不用像平常调用函数那样的方式来调用方法,而是直接将方法内的代码通过一定的修改后copy到原代码中(将方法主体直接插入到调用处,而不是进行方法调用)。这样可以让代码执行的更快(因为省略了调用函数的开销)
另一方面,私有方法也被编译器隐式修饰为final,这意味着private
final void f()和private void f()并无区别。
近期在开发一个项目的后台时,使用到了邮件发送的服务。由于考虑变化的可能性不大,将邮箱地址和发信服务器使用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); } }
2、final修饰
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); } }
可见,static与否并不会影响Java的编译。
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()并无区别。
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- c++11 + SDL2 + ffmpeg +OpenAL + java = Android播放器
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树