【问题标题】:Why instance variables get initialized before constructor called?为什么实例变量在构造函数调用之前被初始化?
【发布时间】:2013-09-16 14:08:57
【问题描述】:

我有以下这段代码:

public abstract class UCMService{
    private String service;     

    protected DataMap dataMap = new DataMap(); 

    protected class DataMap extends HashMap<String,String> {

        private static final long serialVersionUID = 4014308857539190977L;

        public DataMap(){
            System.out.println("11111");
            put("IdcService",service);
        }
    }

    public UCMService(String service){
        System.out.println("2222");
        this.service = service;
    }
}

现在在控制台中,DataMap 构造函数的 System.out.printlnUCMService 构造函数之前执行。

我想知道为什么会这样。

【问题讨论】:

    标签: java constructor


    【解决方案1】:

    这是因为在编译时,编译器会将您在声明位置所做的每个初始化都移动到您类的每个构造函数中。所以UCMService类的构造函数被有效编译为:

    public UCMService(String service){
        super();  // First compiler adds a super() to chain to super class constructor
        dataMap = new DataMap();   // Compiler moves the initialization here (right after `super()`)
        System.out.println("2222");
        this.service = service;
    }
    

    所以,显然DataMap() 构造函数在UCMService 类的print 语句之前执行。同样,如果您的 UCMService 类中有更多构造函数,初始化将移至所有构造函数。


    我们来看一个简单类的字节码:

    class Demo {
        private String str = "rohit";
    
        Demo() {
            System.out.println("Hello");
        }
    }
    

    编译这个类,并执行命令-javap -c Demo。你会看到如下构造函数的字节码:

    Demo();
        Code:
           0: aload_0
           1: invokespecial #1   // Method java/lang/Object."<init>":()V
           4: aload_0
           5: ldc           #2   // String rohit
           7: putfield      #3   // Field str:Ljava/lang/String;
          10: getstatic     #4   // Field java/lang/System.out:Ljava/io/PrintStream;
          13: ldc           #5   // String Hello
          15: invokevirtual #6   // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          18: return
    

    在第7行可以看到putfield指令,将str字段初始化为"rohit",在print语句之前(指令在15行)

    【讨论】:

    • 真的很精彩的解释。 BTW,我没有尝试过,但是每次遇到初始化顺序问题我可能想用一些工具查看编译器生成的代码。
    【解决方案2】:

    简答
    因为规范是这么说的。

    长答案
    构造函数不能使用内联初始化的字段会很奇怪。

    你希望能够写作

    SomeService myService = new SomeService();
    public MyConstructor() {
        someService.doSomething();
    }
    

    【讨论】:

    • 等等,所以有人告诉我不要在构造函数中工作。我认为这意味着,只在构造函数中分配变量。 someService.doSomething() 算不算工作?
    • 如果 doSomething() 确实做了某事,那么是的——这有效。
    • @nos 如果doSomething() 没有做某事,那么它应该被重命名为更具描述性的东西。
    • @InspiredOne:构造函数应该做任何必要的事情来产生一个有效的对象实例。虽然有时让对象以一种唯一可以对它们做的事情就是调用初始化方法的模式启动是合理的,但通常 [并不总是] 设计一个类型更有帮助,以便没有“尚未 -有用的”模式。话虽如此,使用字段初始化器来创建需要清理的东西是危险的,因为如果构造函数失败,就无法清理它们。
    猜你喜欢
    • 2015-09-15
    • 2023-03-03
    • 2015-04-28
    • 1970-01-01
    • 2015-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多