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

一、背景引入

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