为什么实例字段的值为空?

时间:2015-09-24 20:48:43

标签: java inheritance constructor override abstract-class

我有这段简单的代码。

abstract class X {
    X() {
        read();
    }

    private void read() {
        Object obj = new Object();
        readValue(obj);
    }
    protected abstract void readValue(Object obj);
}

class Y extends X {

    Object obj = null;
    Y() {
        super();
    }

    @Override
    protected void readValue(Object obj) {
        this.obj = obj;
    }

    void printer() {
        System.out.println("Object = " + obj);
    }
}

class Runner {
    public static void main(String[] args) {
        Y y = new Y();
        y.printer();
    }
}

当我运行上面的代码时,对象被打印为null。 (我得到" Object = null"
令人惊讶的是,在Y类中我删除了null声明

Object obj;

打印对象的实际值。
类似的东西(" Object = java.lang.Object@3cd1a2f1"
为什么会出现这种行为?什么是'这个'指向?如果我们只是声明它,那么任何对象都被null初始化,那么为什么会出现这种异常行为?

3 个答案:

答案 0 :(得分:3)

obj字段为空的原因是由于Y的构造函数调用中发生的步骤序列:

  1. Y的构造函数调用超级构造函数,该构造函数最终调用具体类readValue的{​​{1}},因此为Y字段分配非空值。 / LI>
  2. 超级构造函数完成后,由于变量初始值设定项,实例字段obj被初始化为null:

    obj
  3. 删除Object obj = null; 初始值设定项后,它将成为一个简单的字段声明,不会在步骤2中执行实例初始化。

    apt解决方案不是删除null初始值设定项,而是重新设计整个类层次结构。例如,由于null的目的似乎只是变量的setter,因此您不需要使它覆盖父类中的抽象方法。只需将其设置为单独的方法,并在readValue的构造函数完成后调用它。

答案 1 :(得分:2)

这说明了从超类构造函数中调用子类中的继承方法的危险。主要的危险是,在超类构造函数完成之后,子类中变量的初始化器运行

以下是发生的事情。

  1. 创建y的对象。
  2. 调用超类构造函数X(),调用read()
  3. read方法会创建一个新的Object并将其传递给readValueY实现了这一点。
  4. readValue中的Y方法将obj设置为新对象。
  5. 超类构造函数X()完成,初始化程序在Y中运行 ,将obj设置为null
  6. printer方法打印"Object = null"
  7. 如果您删除obj中的Y声明,则无法运行初始化程序,obj变量会保留其值。

    JLS, Section 12.5,声明:

      

    [A]将新对象中的实例变量(包括在超类中声明的变量)初始化为其默认值(§4.12.5)。

         

    在作为结果返回对新创建的对象的引用之前,处理指示的构造函数以使用以下过程初始化新对象:

         
        
    1. 将构造函数的参数分配给此构造函数调用的新创建的参数变量。

    2.   
    3. 如果此构造函数以同一个类中的另一个构造函数的显式构造函数调用(第8.8.7.1节)开头(使用此方法),则使用这五个相同步骤计算参数并以递归方式处理该构造函数调用。如果该构造函数调用突然完成,则此过程突然完成,原因相同;否则,继续步骤5.

    4.   
    5. 此构造函数不以同一类中另一个构造函数的显式构造函数调用开头(使用此方法)。如果此构造函数用于Object以外的类,则此构造函数将以超类构造函数的显式或隐式调用开始(使用super)。使用这五个相同的步骤评估参数并递归处理超类构造函数调用。如果该构造函数调用突然完成,则此过程突然完成,原因相同。否则,请继续执行步骤4.

    6.   
    7. 为此类执行实例初始值设定项和实例变量初始值设定项,将实例变量初始值设定项的值分配给相应的实例变量,按从而出现的从左到右的顺序在文本的源代码中。如果执行任何这些初始值设定项导致异常,则不会处理其他初始化程序,并且此过程会突然完成同样的异常。否则,请继续步骤5.

    8.   
    9. 执行此构造函数的其余部分。如果执行突然完成,则此过程突然完成,原因相同。否则,此过程正常完成。

    10.   

    (强调我的)

      

    与C ++不同,Java编程语言在创建新类实例期间未指定方法分派的更改规则。如果调用在被初始化的对象的子类中重写的方法,则使用这些重写方法,即使在新对象完全初始化之前也是如此。

答案 2 :(得分:0)

该对象为null,因为超类构造函数在子类构造函数之前运行,因此在调用超类构造函数之后执行语句 Object obj = null; 是很自然的。

分配 Object obj = null ;在编译期间内联到构造函数中。这只能在现有实例中访问, 当你在构造函数中时它仍然不存在(它仍然在构建中)。

您可以通过将对象声明为静态来实现对象值(Object = java.lang.Object@3cd1a2f1)。

静态对象obj = null;

一般来说,从构造函数中实时调用overriden方法是不好的做法。