精华帖 (0) :: 良好帖 (3) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2012-09-21
最后修改:2012-09-22
之前有很多前辈写过我们经常犯的一些低级错误:
在此我再补充一个,好时刻提醒自我保持清醒! 这个是关于final变量的,有时候我们可能定义一些常量类 这里边存放了我们定义的一些常量,有时候我们可能修改,修改后有时候为了简单 仅仅把这个类编译后 部署到服务器,此时我们可能看到的还是老的数据。这是因为final变量的编译期优化造成的,所有使用final变量的位置会通过值副本替换 而不是引用。测试场景Hotspot1.6
public class A { //网站的管理员ID public static final int ADMIN_ID = 10000001; }
public class B { public static void main(String[] args) { int adminId = A.ADMIN_ID; System.out.println(adminId); } }
此时通过 javac *.java 编译所有类; java B 运行结果是没有问题的。
接下来A的ADMIN_ID变了,我修改为10000002
public static final int ADMIN_ID = 10000002;
javac A.java 编译A类; Java B 运行结果还是10000001。 此时我们通过javap -verbose B 查看B的字节码
public static void main(java.lang.String[]); Code: Stack=2, Locals=2, Args_size=1 0: ldc #2; //int 10000001 2: istore_1 3: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream; 6: iload_1 7: invokevirtual #4; //Method java/io/PrintStream.println:(I)V 10: return 此时我们看到ldc #2;//int 10000001 没有变,到此我们知道原因了,这是编译的优化造成的。
怎么解决呢? 1、把所有相关类编译部署; 2、通过如下方案解决:
public class A { //网站的管理员ID public static final int ADMIN_ID; static { ADMIN_ID = 10000003; } }
此时ADMIN_ID的初始化 放在static初始化块中,编译期不会发生优化。再查看B类字节码,发现是通过getstatic获取常量的:
public static void main(java.lang.String[]); Code: Stack=2, Locals=2, Args_size=1 0: getstatic #2; //Field A.ADMIN_ID:I 3: istore_1 4: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream; 7: iload_1 8: invokevirtual #4; //Method java/io/PrintStream.println:(I)V 11: return
同理 对应final实例变量/局部变量也是有这个问题的: public class C { //网站的管理员ID public final int ADMIN_ID = 10000001; }
public class D { public static void main(String[] args) { int adminId = new C().ADMIN_ID; System.out.println(adminId); } }
我们仅需要将C改为:
public class C { //网站的管理员ID public final int ADMIN_ID; { ADMIN_ID = 10000001; } } 或
public class C { //网站的管理员ID public final int ADMIN_ID; public C() { ADMIN_ID = 10000001; } }
=========================================================================================== 补充一个读取配置文件加载常量的类(包括:默认配置文件 和 增量配置文件),在大多数需要可配置的项目中都可以采用此方法。
采用读取配置文件加载常量/配置信息的方式更可取,这样可以直接修改配置文件,而不需要重新编译我们的常量类,实现配置和代码分离。
package cn.javass; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * 统一常量类 通过加载配置文件获取常量(方便修改) * @author Zhang Kaitao * */ public class Constants { //默认配置文件(和当前类同目录) private static final String DEFAULT_CONFIG_FILE = "constants-defualt.properties"; //增量配置文件(在classpath) private static final String CLASSPATH_CONFIG_FILE = "constants.properties"; private static final Properties props = new Properties();; public static final int ADMIN_ID; static { loadProperties(); ADMIN_ID = Integer.valueOf((String)props.get("admin.id")); } /** * 获取其他常量 * @param <T> * @param key 配置文件中的常量key * @return 返回配置文件中对应的键 */ public static String getAsString(String key) { return (String)props.get(key); } public static Integer getAsInteger(String key) { return Integer.valueOf((String)props.get(key)); } public static Long getAsLong(String key) { return Long.valueOf((String)props.get(key)); } public static Double getAsDouble(String key) { return Double.valueOf((String)props.get(key)); } private static void loadProperties() { InputStream is = null; //load default config try { is = Constants.class.getResourceAsStream(DEFAULT_CONFIG_FILE); props.load(is); } catch (Exception e) { //TODO 记录log e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } //load classpath config try { is = Thread.currentThread().getContextClassLoader().getResourceAsStream(CLASSPATH_CONFIG_FILE); if(is != null) { props.load(is); } } catch (Exception e) { //TODO 记录log e.printStackTrace(); } finally { try { if(is != null) { is.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
package cn.javass; public class Test { public static void main(String[] args) { System.out.println(Constants.ADMIN_ID); System.out.println(Constants.getAsDouble("admin.id")); } }
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2012-09-21
没有遇到过
|
|
返回顶楼 | |
发表时间:2012-09-21
zhua12 写道 没有遇到过
之前我犯过类似的错误。为了省事 就直接编译一个类放服务器上 |
|
返回顶楼 | |
发表时间:2012-09-21
jinnianshilongnian 写道 zhua12 写道 没有遇到过
之前我犯过类似的错误。为了省事 就直接编译一个类放服务器上 确实很容易犯的错误 |
|
返回顶楼 | |
发表时间:2012-09-21
gaiqiliang 写道 jinnianshilongnian 写道 zhua12 写道 没有遇到过
之前我犯过类似的错误。为了省事 就直接编译一个类放服务器上 确实很容易犯的错误 这个只有没有用如maven、ant等工具时 可能就拷贝了。。 |
|
返回顶楼 | |
发表时间:2012-09-21
不错,学习了,我之前就遇到过类似的问题,改了常量值上传到服务器上,就是不对!
后来反编译后才发现没改过来,所有引用静态常量的类都得重新编译一遍才行,以后就可以采用楼主推荐的这种方法了,采用构造方法或静态块的方式!! |
|
返回顶楼 | |
发表时间:2012-09-21
superich2008 写道 不错,学习了,我之前就遇到过类似的问题,改了常量值上传到服务器上,就是不对!
后来反编译后才发现没改过来,所有引用静态常量的类都得重新编译一遍才行,以后就可以采用楼主推荐的这种方法了,采用构造方法或静态块的方式!! 遇到同道人了 第一次遇到很难想象! |
|
返回顶楼 | |
发表时间:2012-09-22
用static block来初始化initial感觉并不好.
final name= immutablevalue <=> immutablevalue 是非常自然地推理并且是编译器在抽象模型下应有的优化。而在这之上弄一个 static block 放在代码里是无谓的语义复杂度增加. 这里的直接错误是编译单一文件扔上服务器。从这个直接错误来看,理想的方法是增量编译 自动 编译 相关dependency的文件或者其它类似做法. 当然,具体情况我不清楚,可能是具体困难导致无法这么做,不过我觉得还是该指出我认为 这里用static block 是比较trick的做法,能够的话,还是不要改代码的好. |
|
返回顶楼 | |
发表时间:2012-09-22
tlde_ti 写道 用static block来初始化initial感觉并不好.
final name= immutablevalue <=> immutablevalue 是非常自然地推理并且是编译器在抽象模型下应有的优化。而在这之上弄一个 static block 放在代码里是无谓的语义复杂度增加. 这里的直接错误是编译单一文件扔上服务器。从这个直接错误来看,理想的方法是增量编译 自动 编译 相关dependency的文件或者其它类似做法. 当然,具体情况我不清楚,可能是具体困难导致无法这么做,不过我觉得还是该指出我认为 这里用static block 是比较trick的做法,能够的话,还是不要改代码的好. 这个倒是,但是当比如加载配置文件来初始化一些常量,就可以使用这种方式,或者直接如 final name = loadName() 通过读配置文件加载。 其实我更加建议通过读取配置文件来加载一些配置常量,这样改配置文件即可 |
|
返回顶楼 | |
发表时间:2012-09-22
补充一个读取配置文件加载常量的类(包括:默认配置文件 和 增量配置文件),在大多数需要可配置的项目中都可以采用此方法。
采用读取配置文件加载常量/配置信息的方式更可取,这样可以直接修改配置文件,而不需要重新编译我们的常量类,实现配置和代码分离。
package cn.javass; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * 统一常量类 通过加载配置文件获取常量(方便修改) * @author Zhang Kaitao * */ public class Constants { //默认配置文件(和当前类同目录) private static final String DEFAULT_CONFIG_FILE = "constants-defualt.properties"; //增量配置文件(在classpath) private static final String CLASSPATH_CONFIG_FILE = "constants.properties"; private static final Properties props = new Properties();; public static final int ADMIN_ID; static { loadProperties(); ADMIN_ID = Integer.valueOf((String)props.get("admin.id")); } /** * 获取其他常量 * @param <T> * @param key 配置文件中的常量key * @return 返回配置文件中对应的键 */ public static String getAsString(String key) { return (String)props.get(key); } public static Integer getAsInteger(String key) { return Integer.valueOf((String)props.get(key)); } public static Long getAsLong(String key) { return Long.valueOf((String)props.get(key)); } public static Double getAsDouble(String key) { return Double.valueOf((String)props.get(key)); } private static void loadProperties() { InputStream is = null; //load default config try { is = Constants.class.getResourceAsStream(DEFAULT_CONFIG_FILE); props.load(is); } catch (Exception e) { //TODO 记录log e.printStackTrace(); } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } //load classpath config try { is = Thread.currentThread().getContextClassLoader().getResourceAsStream(CLASSPATH_CONFIG_FILE); if(is != null) { props.load(is); } } catch (Exception e) { //TODO 记录log e.printStackTrace(); } finally { try { if(is != null) { is.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
package cn.javass; public class Test { public static void main(String[] args) { System.out.println(Constants.ADMIN_ID); System.out.println(Constants.getAsDouble("admin.id")); } }
|
|
返回顶楼 | |