您的位置:首页 > 编程语言 > Java开发

final关键字与Java编译器的小坑

2016-04-05 14:59 302 查看
一、背景引入

近期在开发一个项目的后台时,使用到了邮件发送的服务。由于考虑变化的可能性不大,将邮箱地址和发信服务器使用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 编译 final 关键字