这绝不是一个权威的答案,但让我做一个有根据的猜测。
有一个根本的区别,我认为其他问题的答案与这个区别有关。
它存在于类型初始化的顺序中,尤其是在继承的上下文中。
那么,实例初始化是如何工作的呢?
在 C# 中:
ctor 相互调用或(明确)调用基类的 ctor 的可能性不会改变这种情况,所以我将把它排除在外。
基本上发生的是,这对链中的每个 chas 运行,从最衍生的开始:
Derived.initialize(){
derivedInstance.field1 = field1Initializer();
[...]
Base.Initialize();
Derived.Ctor();
}
一个简单的例子说明了这一点:
void Main()
{
new C();
}
class C: B {
public int c = GetInt("C.c");
public C(){
WriteLine("C.ctor");
}
}
class B {
public int b = GetInt("B.b");
public static int GetInt(string _var){
WriteLine(_var);
return 6;
}
public B(){
WriteLine("B.ctor");
}
public static void WriteLine(string s){
Console.WriteLine(s);
}
}
输出:
C.c
B.b
B.ctor
C.ctor
这意味着如果访问字段初始值设定项中的字段是有效的,我可能会遇到这样的灾难:
class C: B {
int c = b; //b is a field inherited from the base class, and NOT YET INITIALIZED!
[...]
}
在 Java 中:
关于类型初始化的长篇有趣的文章在这里。总结一下:
它有点复杂,因为除了实例字段初始化器的概念之外,还有(可选)实例初始化器的概念,但这里是它的要点:
一切都沿着继承链运行。
- 基类的实例初始化程序运行
- 基类的字段初始化器运行
基类的 ctor(s) 运行
对继承链中的下一个类重复上述步骤。
- 重复上一步,直到到达最派生的类。
这是证明:(或自己在线运行)
class Main
{
public static void main (String[] args) throws java.lang.Exception
{
new C();
}
}
class C extends B {
{
WriteLine("init C");
}
int c = GetInt("C.c");
public C(){
WriteLine("C.ctor");
}
}
class B {
{
WriteLine("init B");
}
int b = GetInt("B.b");
public static int GetInt(String _var){
WriteLine(_var);
return 6;
}
public B(){
WriteLine("B.ctor");
}
public static void WriteLine(String s){
System.out.println(s);
}
}
输出:
init B
B.b
B.ctor
init C
C.c
C.ctor
这意味着,在字段初始化程序运行时,所有继承的字段都已经初始化(通过基类中的初始化程序或 ctor),因此允许这种行为是足够安全的:
class C: B {
int c = b; //b is inherited from the base class, and it's already initialized!
[...]
}
在 Java 中,就像在 C# 中一样,字段初始值设定项按声明顺序运行。
Java 编译器甚至会检查字段初始值设定项是否被乱序调用*:
class C {
int a = b; //compiler error: illegal forward reference
int b = 5;
}
* 顺便说一句,如果初始化程序调用实例方法,您可以乱序访问字段:
class C {
public int a = useB(); //after initializer completes, a == 0
int b = 5;
int useB(){
return b; //use b regardless if it was initialized or not.
}
}