Java中static final“常量”的坑

在Java中,其实语言本身并没有“常量”这样的概念。但是我们根据常量的特点(不可修改),经常会使用“static final”关键字,对一个变量进行修饰,以达到不创建类就可直接使用,并且该变量不允许修改的目的,看起来就跟“常量”一样。无论是教科书,还是上网查资料,都会教我们这样写

这样的写法在纯class文件发布的环境,没有问题,因为class文件只要一修改就会被eclipse重新编译。但在Jar包发布的环境中,就会出问题了。

现在假如我在JarA.jar中定义了一个“常量”:

public static final String MY_CONST = "123";

然后在JarB.jar中进行引用:

String str = xxxx.MY_CONST

然后有一天,我修改了JarA中MY_CONST的值,变成456,并重新编译成JarA.jar(JarB.jar保持不变)。运行程序,你会发现,程序显示出来的值,依然是123。无论你重新发布多少遍JarA,都没用!不是已经修改了JarA中MY_CONST的值了吗,为什么不生效??这让人太费解了。。。

这里就要涉及到Java的编译机制问题了。在Java编译中,final的值,编译后是不会保持对原变量的引用的,而会直接把该变量的值,编译进代码中。也就是说,在JarB.jar的class文件中,实际的字节码并不是String str = xxxx.MY_CONST,而是String str = "123"!所以无论你JarA中MY_CONST的值怎么修改,JarB中都不会发生任何改变!因此,每当修改了JarA中常量的值后,引用到这个常量的所有Jar包,必须跟着重新编译一次才行!

不得不说,这样的运行环境,实在是太危险了!万一以后Jar包增多了,少编译了一个引用的Jar包,都会发生莫名奇妙的错误!

到此得出结论:

看来我们的习惯要适当改改了。。。如果我们的代码是将会被编译进Jar包的话,还是谨慎使用static final的变量吧(Jar包范围内使用没问题,如果是会被跨Jar包引用到的,一定要小心)

去掉final关键字后,代码编译时就会保持对原变量的引用关系,而不会直接把变量的值编译进class文件中。虽然没了final关键字,变量就没了只读约束,但总比代码出现莫名其妙的错误要好的多(而且这个传递的影响范围可能很广)。程序员必须遵循好规范约束,不对全大写命名的“常量”做修改,就可以了


未完待续:

后来,我发现了一个既可以保持变量拥有final标识,又可以避免文中所提编译问题的方法。在声明常量的时候,可以用以下的方式进行声明:

public static final String MY_CONST = getConst();

private static String getConst() {
    return "123";
}

或者

public class MyClass {

    // 对外的常量
    public static final String MY_CONST = ConstProvider.MY_CONST;

    private static class ConstProvider {
        // 把真正的常量值,放在一个内部类当中
        public static String MY_CONST = "123";    
    }
}

虽然有点绕,但是可以让调用方保持了MY_CONST这个常量的引用,而不是直接把123编译进程序中。有兴趣的朋友,可以自己写段简单的程序,发布成Jar包再反编译一下,就会看到这两种写法的区别。

话说,既然作为常量,相信绝大部分情况下,都是不会变的。例如PI,永远都是3.1415926xxxx。所以Java直接把常量的值直接编译进去,也说得过去。

不过我们在实际开发系统的时候,有时却很难保证常量的值不会改变。我们声明常量,更多只是为了方便改一个地方,就把全部地方都改过来这样。这时就要注意文中所提到的问题了。

猜你喜欢

转载自blog.csdn.net/Harryfin/article/details/84618865