我的私有成员变量怎么会被设置为null?

3

我已经专业地使用Java编程超过十年了。这是我曾经试图追踪的最奇怪的错误之一。我有一个私有成员,我对其进行初始化,然后它会自行更改为null。

public class MyObject extends MyParent
{
    private SomeOtherClass member = null;

    public MyObject()
    {
        super();
    }

    public void callbackFromParentInit()
    {
        member = new SomeOtherClass();
        System.out.println("proof member initialized: " + member);
    }

    public SomeOtherClass getMember()
    {
        System.out.println("in getMember: " + member);
        return member;
    }
}

输出:

proof member initialized: SomeOtherClass@2a05ad6d
in getMember: null

如果您运行此代码,显然它将正常工作。在我的实际代码中,只有这三个出现(如果计算println,则为五个),并且呈现出确切的模式。
我是否遇到了JVM中的某些错误?除非我错了,否则父类无法干扰私有成员,并且无论我在代码行之间放置什么,都无法在不使用标识符“member”的情况下更改成员的值。

2
你有一个完整的工作示例吗? - Sundae
1
以下是代码的输出结果: MyObject foo = new MyObject(); foo.callbackFromParentInit(); foo.getMember(); - davidbuzatto
我不能发布工作代码。但我可以告诉你,“member”标识符在代码中仅以所示的模式使用了五次。除非在此类的另一行中使用标识符,否则不应该有任何方法将其更改为null。 - Greg Valcourt
在类实例化时,您将其设为null,并且仅在函数callbackFromParentInit()的范围内进行初始化。当您调用“getMember()”时,它返回类的空值,因为它从未针对类的范围进行过实例化。 - Evan Bechtol
无法保证“在getMember中的println”来自打印“proof member initialized”的相同实例。 - Gus
有趣的是:MyObject foo = new MyObject(); foo.callbackFromParentInit(); foo.getMember(); - 成功了:证明成员已初始化:SomeOtherClass@77f80d16,在getMember中:SomeOtherClass@77f80d16。 - Greg Valcourt
4个回答

11
这是因为成员变量初始化和构造函数调用的顺序问题。
你正在从超类MyParent的构造函数中调用callbackFromParentInit()方法。当此方法被调用时,它会设置member。但在此之后,对象初始化的子类部分被执行,并且会执行member的初始化器,将member设置为null
例如,请参见: 构造函数的调用顺序和字段的初始化顺序在Java语言规范的第12.5段中有描述。

3

在执行父类构造函数后,将null赋值给member字段。


0
解决方法是更改:
private SomeOtherClass member = null;

到:

private SomeOtherClass member;

这很有趣,我以前从未使用过这种确切的初始化模式。它看起来像一个愚蠢的顺序。 - Greg Valcourt
2
好的,这解决了即时问题,但真正的解决方案是不要从超类构造函数中调用可重写方法。请参见此问题 - Jesper

0

千万不要在超类的构造函数中调用非final方法。

这被认为是一种不好的做法,因为它可能导致令人讨厌且难以调试的错误,就像你现在遇到的这个问题。

在类X的构造函数中执行X的初始化。不要依赖Java对层次结构的初始化顺序。如果无法初始化类属性,例如因为它有依赖关系,请使用构建器或工厂模式。

在这里,子类正在将属性member重置为null,这是由于超类和子类的构造函数和初始化块的执行顺序所致,正如前面提到的,您不应该依赖这一点。

有关构造函数、层次结构和this引用的隐式逃逸的概念,请参考这个相关问题

我只能想到遵循一套(可能不完整的)规则/原则来避免这个问题和其他类似的问题:

  • 只从构造函数内部调用private方法

  • 如果你喜欢刺激并想从构造函数内部调用protected方法,那就这么做吧,但是将这些方法声明为final,这样它们就不能被子类重写了

  • 不要在构造函数中创建任何内部类,包括匿名、本地、静态或非静态的内部类

  • 在构造函数中,不要直接将this作为参数传递给任何东西

  • 避免以上规则的任何可转换组合,即不要在从构造函数内部调用的privateprotected final方法中创建匿名内部类

  • 仅使用构造函数来构造类的实例,并让它仅初始化类的属性,可以使用默认值或提供的参数


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接