2010-07-20 141 views
3

赫雷什一些示例代码,问题的实例变量初始化

class Base 
{ 
    private int val; 

    Base() { 
    val = lookup(); 
    } 

    public int lookup() { 
    //Perform some lookup 
    // int num = someLookup(); 
    return 5; 
    } 

    public int value() { 
    return val; 
    } 
} 

class Derived extends Base 
{ 
    private int num = 10; 

    public int lookup() { 
    return num; 
    } 
} 


class Test 
{ 
    public static void main(String args[]) { 

    Derived d = new Derived(); 
    System.out.println("d.value() returns " + d.value()); 

    } 
} 

输出:d.value()返回0 //我预期10查找()被重写,但不是0!有人可以澄清这一点?

Derived的实例变量的初始化在其查找方法执行时未发生。如何确保在调用其方法时初始化实例变量Derived

+0

'value()'仅在Base中定义,并返回仅在Base中定义的'val',在Base构造函数中由'5'初始化。 'lookup'是从Base构造函数调用的,这意味着重载不起作用,因为Derived类尚未完全构建。您还期望从代码中得到什么? – ULysses 2010-07-20 09:36:11

+0

@ULysses,在发布的代码中重载?我认为你的意思是压倒性的。 – Zaki 2010-07-20 10:01:47

+0

@ULysses,为什么没有压倒一切的工作? – Zaki 2010-07-20 10:03:34

回答

8

一开始,该代码不编译由于缺乏someLookup方法:

如果我理解正确的话你的意图,你应该改变value方法Base是。

无论如何,除此之外,我相信你的问题是你的期望是无效的,因为构造函数是分层运行的方式。

超类的构造函数总是在子类之前运行,这包括子类变量的初始化方法(它们实际上是作为构造函数的一部分运行的)。所以,当你创建的Derived你的情况下,会发生以下情况:

  1. Base构造函数首先被调用。
  2. lookup()被调用,它使用Derived中的实现。
  3. num返回,这是此时的默认值,因为Derived的构造函数和初始值设定项尚未运行
  4. val设置为0
  5. Derived初始化和构造都运行 - 呼叫lookup点上会返回10。

一般来说,由于这个原因,从构造函数中调用非最终方法是一个坏主意,许多静态分析工具都会警告你。这与在构建过程中让对象引用泄露相似,最终可能会导致类级别不变量实例失效(对于您的情况,Derived的num“始终”为10,但在某些点可以看到它为0)。

编辑:请注意,此特殊情况下,无需任何额外的代码,你可以通过使num解决问题的常数:

class Derived extends Base 
{ 
    private static final int num = 10; 
    ... 

这实际上做你想做的,因为静态初始化在类被加载时运行(这必须在调用构造函数之前发生)。然而,这确实适用于:

a)该类的所有实例共享相同的num变量; b)num永不需要改变(如果这是真的,那么(a)自动为真)。

在给出的确切代码中显然是这种情况,但我希望您为简洁起见可能会省略其他功能。

我在这里包括这个比较和兴趣,不是因为它是一般意义上的这个“问题”的解决方法(因为它不是)。

+0

lookup()只是虚拟的,可以注释掉它。 – Zaki 2010-07-20 09:43:38

+0

关于Java构造函数和模板方法模式的相关文章:http://novyden.blogspot.com/2011/08/java-anti-pattern-constructors-and.html – topchef 2011-08-08 08:04:30

4

你得到0返回的原因是构造函数Base在Derived中被赋值为num之前被调用(并且在Derived中调用查找)。

通常,在派生实例字段被初始化之前调用基础构造函数。

+0

这意味着派生类的方法可以运行,即使其实例变量尚未初始化? – Zaki 2010-07-20 10:08:53

+0

@Zaki,没错。正如Andrzej Doyle所说,这就是为什么从构造函数中调用非最终方法是一个坏主意。我同意这种行为是违反直觉的,即使它具有一定的一致性(基类在派生类之前初始化)。 – andrewmu 2010-07-20 10:42:20

1

你重写方法lookup()Derived类,所以当Base调用构造函数调用从Derived哪个机构return num的方法。在初始化Base时,Derivednum实例变量尚未初始化并且为0.这就是为什么在Base中将val分配给0的原因。那么

public int value() { 
return lookup(); 
} 
1

当构造函数调用此函数时,下面的代码片段返回0(通过查看程序,您期望为10)。简单的原因是num尚未初始化,父类调用此方法。

public int lookup() { 
    return num; 
} 
2

在一个可以在子类中重写的构造函数中调用方法通常是个坏主意。在您的例子会发生以下情况:

  • 派生构造函数被调用
    • 基类的构造被称为第一个动作
    • 基本构造函数调用查询
  • 派生构造仍在继续,initialies NUM到10

由于子类构造函数在基础构造函数调用lookup时没有完成,对象尚未完全初始化,查找返回num字段的默认值。

+0

+1,经验教训:这确实是不好的做法从构造函数中调用非final方法。 – Zaki 2010-07-20 10:46:22

2

让我们慢慢来:

class Test 
{ 
    public static void main(String args[]) { 
    // 1 
    Derived d = new Derived(); 
    // 2 
    System.out.println("d.value() returns " + d.value());  
    } 
} 

第1步,调用(默认值)上构造派生,之前设置NUM = 10,它链条高达基地的构造函数,调用Derived的查找方法,但NUM尚未设置,因此val仍未初始化。

第2步,你调用d.value(),它属于Base,并且val由于1而未被设置,因此你得到0而不是10。

2

已经有很多伟大的答案对为什么而构建的基类,你不能访问子领域,但我想你问一个如何:这样的事情一个有效的解决方案:

public abstract class Animal { 
    public Animal() { 
    System.println(whoAmI()); 
    } 
    public abstract String whoAmI(); 
} 

public Lion() extends Animal { 
    private String iAmA = "Lion"; 
    public Lion(){super();} 
    public String whoAmI() {return iAmA;} 
} 

实用的方法是在基类引入的init()方法从子类的构造函数中调用它,如:

public abstract class Animal { 
    private boolean isInitialized = false; 
    public Animal() {} 
    void init() { 
    isInitialized = true; 
    System.out.println(whoAmI()); 
    } 
    public abstract String whoAmI(); 
    public void someBaseClassMethod() { 
    if (!isInitialized) 
     throw new RuntimeException("Baseclass has not been initialized"); 
    // ... 
    } 
} 

public Lion() extends Animal { 
    private String iAmA = "Lion"; 
    public Lion() { 
    super(); 
    init(); 
    } 
    public String whoAmI() {return iAmA;} 
} 

唯一的问题是,你无法强制子类在基类上调用init()方法,并且基类可能未正确初始化。但是有了一个标志和一些例外情况,我们可以在运行时提醒程序员他应该叫init() ...

+0

从子类构造函数调用init()时出现的一个问题是,这样的方法不能写入任何“final”字段。如果每个级别的构造函数都接受一个'object',它将它传递给超类的构造函数,或者将其封装在一个给予超类构造函数的对象中,并且每个级别都有一个虚拟方法'getConstructorParam()'返回'object'被传递给它的构造函数(如果需要,从'super.getConstructorParam()''返回的类中提取),那么子类应该能够在基类构造函数完成之前完成它们初始化的一部分。 – supercat 2013-05-21 19:34:04

+0

我会质疑任何设计的最终领域是由一个子类初始化;) – 2013-05-22 10:24:53