论坛首页 Java企业应用论坛

再谈一个关于final的不一致编译的低级错误

浏览 10436 次
精华帖 (0) :: 良好帖 (3) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-09-21   最后修改:2012-09-22

之前有很多前辈写过我们经常犯的一些低级错误:

一个隐形的java int溢出

竟然犯下这种低级错误,羞愧难当啊!!!

 

在此我再补充一个,好时刻提醒自我保持清醒! 这个是关于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"));
        
    }
}

 

 

 

  • 大小: 15.3 KB
   发表时间:2012-09-21  
没有遇到过
0 请登录后投票
   发表时间:2012-09-21  
zhua12 写道
没有遇到过

之前我犯过类似的错误。为了省事 就直接编译一个类放服务器上
0 请登录后投票
   发表时间:2012-09-21  
jinnianshilongnian 写道
zhua12 写道
没有遇到过

之前我犯过类似的错误。为了省事 就直接编译一个类放服务器上

确实很容易犯的错误
0 请登录后投票
   发表时间:2012-09-21  
gaiqiliang 写道
jinnianshilongnian 写道
zhua12 写道
没有遇到过

之前我犯过类似的错误。为了省事 就直接编译一个类放服务器上

确实很容易犯的错误

这个只有没有用如maven、ant等工具时 可能就拷贝了。。
0 请登录后投票
   发表时间:2012-09-21  
不错,学习了,我之前就遇到过类似的问题,改了常量值上传到服务器上,就是不对!
后来反编译后才发现没改过来,所有引用静态常量的类都得重新编译一遍才行,以后就可以采用楼主推荐的这种方法了,采用构造方法或静态块的方式!!
0 请登录后投票
   发表时间:2012-09-21  
superich2008 写道
不错,学习了,我之前就遇到过类似的问题,改了常量值上传到服务器上,就是不对!
后来反编译后才发现没改过来,所有引用静态常量的类都得重新编译一遍才行,以后就可以采用楼主推荐的这种方法了,采用构造方法或静态块的方式!!

遇到同道人了    第一次遇到很难想象!
0 请登录后投票
   发表时间:2012-09-22  
用static block来初始化initial感觉并不好.
final name= immutablevalue  <=> immutablevalue 是非常自然地推理并且是编译器在抽象模型下应有的优化。而在这之上弄一个 static block 放在代码里是无谓的语义复杂度增加.

这里的直接错误是编译单一文件扔上服务器。从这个直接错误来看,理想的方法是增量编译 自动 编译 相关dependency的文件或者其它类似做法.

当然,具体情况我不清楚,可能是具体困难导致无法这么做,不过我觉得还是该指出我认为 这里用static block 是比较trick的做法,能够的话,还是不要改代码的好.
0 请登录后投票
   发表时间:2012-09-22  
tlde_ti 写道
用static block来初始化initial感觉并不好.
final name= immutablevalue  <=> immutablevalue 是非常自然地推理并且是编译器在抽象模型下应有的优化。而在这之上弄一个 static block 放在代码里是无谓的语义复杂度增加.

这里的直接错误是编译单一文件扔上服务器。从这个直接错误来看,理想的方法是增量编译 自动 编译 相关dependency的文件或者其它类似做法.

当然,具体情况我不清楚,可能是具体困难导致无法这么做,不过我觉得还是该指出我认为 这里用static block 是比较trick的做法,能够的话,还是不要改代码的好.

这个倒是,但是当比如加载配置文件来初始化一些常量,就可以使用这种方式,或者直接如 final name = loadName() 通过读配置文件加载。

其实我更加建议通过读取配置文件来加载一些配置常量,这样改配置文件即可
0 请登录后投票
   发表时间: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"));
        
    }
}

 

 

0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics