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

Java 为什么实例字段的值为空?,java,inheritance,constructor,overriding,abstract-class,Java,Inheritance,Constructor,Overriding,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;

我有一段简单的代码

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初始化,那么为什么会出现这种异常行为?

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

Y的构造函数调用超级构造函数,超级构造函数最终调用具体类Y的readValue,因此为obj字段分配一个非null值。 超级构造函数完成后,由于变量初始值设定项,实例字段obj初始化为null:

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

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

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

Y的构造函数调用超级构造函数,超级构造函数最终调用具体类Y的readValue,因此为obj字段分配一个非null值。 超级构造函数完成后,由于变量初始值设定项,实例字段obj初始化为null:

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


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

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

下面是发生的情况

将创建y的对象。 调用超类构造函数X,它调用read。 read方法创建一个新对象并将其传递给readValue,readValue在Y中实现。 Y中的readValue方法将obj设置为新对象。 超类构造函数X完成,初始化器现在在Y中运行,将obj设置为null。 打印机方法打印对象=null。 如果删除Y中obj的声明,则没有要运行的初始值设定项,obj变量保留其值

报告指出:

[A] 新对象中的所有实例变量,包括在超类中声明的实例变量,均初始化为默认值§4.12.5

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

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

如果此构造函数以使用此函数的同一类中另一个构造函数的显式构造函数调用§8.8.7.1开始,则使用相同的五个步骤递归地评估该构造函数调用的参数和过程。如果构造函数调用突然完成,那么这个过程也会因为同样的原因突然完成;否则,继续执行步骤5

此构造函数不会以使用此函数的同一类中的另一个构造函数的显式构造函数调用开始。如果此构造函数用于对象以外的类,则此构造函数将以使用super的超类构造函数的显式或隐式调用开始。使用相同的五个步骤递归地评估超类构造函数调用的参数和过程。如果构造函数调用突然完成,那么此过程也会因为同样的原因突然完成。否则,继续执行步骤4

执行该类的实例初始值设定项和实例变量初始值设定项,将实例变量初始值设定项的值按从左到右的顺序分配给相应的实例变量,它们在类的源代码中以文本形式出现。如果执行这些初始值设定项中的任何一个会导致异常,则不会再处理其他初始值设定项,并且此过程会在该异常的情况下突然完成。否则,继续执行步骤5

执行此构造函数主体的其余部分。如果那个执行官 突然完成,然后出于相同的原因突然完成此过程。否则,此过程将正常完成

重点矿山

不同于C++,java编程语言在创建新类实例时没有指定用于方法调度的更改规则。如果调用了在被初始化对象的子类中被重写的方法,那么即使在新对象完全初始化之前,也会使用这些重写方法


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

下面是发生的情况

将创建y的对象。 调用超类构造函数X,它调用read。 read方法创建一个新对象并将其传递给readValue,readValue在Y中实现。 Y中的readValue方法将obj设置为新对象。 超类构造函数X完成,初始化器现在在Y中运行,将obj设置为null。 打印机方法打印对象=null。 如果删除Y中obj的声明,则没有要运行的初始值设定项,obj变量保留其值

报告指出:

[A] 新对象中的所有实例变量,包括在超类中声明的实例变量,均初始化为默认值§4.12.5

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

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

如果此构造函数以使用此函数的同一类中另一个构造函数的显式构造函数调用§8.8.7.1开始,则使用相同的五个步骤递归地评估该构造函数调用的参数和过程。如果构造函数调用突然完成,那么这个过程也会因为同样的原因突然完成;否则,继续执行步骤5

此构造函数不会以使用此函数的同一类中的另一个构造函数的显式构造函数调用开始。如果此构造函数用于对象以外的类,则此构造函数将以使用super的超类构造函数的显式或隐式调用开始。使用相同的五个步骤递归地评估超类构造函数调用的参数和过程。如果构造函数调用突然完成,那么此过程也会因为同样的原因突然完成。否则,继续执行步骤4

执行该类的实例初始值设定项和实例变量初始值设定项,将实例变量初始值设定项的值按从左到右的顺序分配给相应的实例变量,它们在类的源代码中以文本形式出现。如果执行这些初始值设定项中的任何一个会导致异常,则不会再处理其他初始值设定项,并且此过程会在该异常的情况下突然完成。否则,继续执行步骤5

执行此构造函数主体的其余部分。如果该执行突然完成,则此过程出于相同的原因突然完成。否则,此过程将正常完成

重点矿山

不同于C++,java编程语言在创建新类实例时没有指定用于方法调度的更改规则。如果调用了在被初始化对象的子类中被重写的方法,那么即使在新对象完全初始化之前,也会使用这些重写方法


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

对象obj的赋值=null;在编译时内联到构造函数中。这仅在现有实例中可访问, 实例还不存在,当您在构造函数中时,它仍在构造中

您可以实现object valueObject=java.lang。Object@3cd1a2f1通过取消对象对象为静态

静态对象obj=null


但通常,从构造函数实时调用重写的方法是不好的做法。

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

对象obj的赋值=null;在编译时内联到构造函数中。这仅在现有实例中可访问, 实例还不存在,当您在构造函数中时,它仍在构造中

您可以实现object valueObject=java.lang。Object@3cd1a2f1通过取消对象对象为静态

静态对象obj=null 但是,通常情况下,从构造函数实时调用重写的方法是不好的做法