美文网首页
初始化顺序2

初始化顺序2

作者: 浑身演技 | 来源:发表于2016-07-26 15:32 被阅读78次

Q:父类或者子类重写的值为什么会为空?
详细的例子:

abstract class A {
  val x1: String
  val x2: String = "mom"
  println("A: " + x1 + ", " + x2)
}
class B extends A {
  val x1: String = "hello"
  println("B: " + x1 + ", " + x2)
}
class C extends B {
  override val x2: String = "dad"
  println("C: " + x1 + ", " + x2)
}
object A extends App {
  new C
}

输出

A: null, null
B: hello, null
C:hello, dad

只有到了初始化C的构造器的时候,x1和x2才被初始化。因此,当A和B构造器初始化的时候,将会有NullPointerException的风险。

解释:
一个没有被lazy修饰的val变量,将会快速定义。

在没有声明并定义的情况下,完成初始化一个vals的变量将按照下面的顺序:

1.父类先进行完全初始化,才轮到子类
2.否则,将按照变量声明的顺序

一般来说,当一个val变量被子类重写,将会被多次初始化。因此在上面的例子中,x2在每个声明的地方都会定义。这是一个规则。

一个被子类重写的val变量,在父类构造器进行构造的时候会被初始化为null。

小提示:使用编译器参数能够有效地进行分辨初始化顺序。
-Xcheckinit:增加字段属性的运行时检查
使用此参数进行外部测试是不理智的。因为这会使用包装器对字未初始化段属性进行修饰,增加相当多的代码。
当val变量未被初始化,包装器将会抛出异常而不是让它默默出现。另外一个需要注意的是,这也是会增加运行时检查:当你在某处对为此变量进行结合的时候,它将会告诉你关于代码路径的任何事情。

使用案例:

% scalac -Xcheckinit a.scala
% scala -e 'new C'
scala.UninitializedFieldError: Uninitialized field: a.scala: 13
 at C.x2(a.scala:13)
 at A.<init>(a.scala:5)
 at B.<init>(a.scala:7)
 at C.<init>(a.scala:12)

避免出现null值的处理方案

使用lazy进行修饰val变量

abstract class A {
  val x1: String
  lazy val x2: String = "mom"
  println("A: " + x1 + ", " + x2)
}
class B extends A {
  lazy val x1: String = "hello"
  println("B: " + x1 + ", " + x2)
}
class C extends B {
  override lazy val x2: String = "dad"
  println("C: " + x1 + ", " + x2)
}
object A extends App {
  new C
}

通常来说,这是最好的解决方案。不过不幸的是,你不能声明一个抽象的lazy val变量。出现下面情况时候,你的选择包括:

1.声明一个抽象的val,并且希望子类实现它的时候必须使用lazy进行修饰或者使用早期定义。如果不这么做,在进行构造的过程中,变量在某些使用的地方将会显示为未初始化;
2.声明一个抽象的def,并且希望子类实现它的时候必须使用lazy进行修饰。如果不这么做,每次重新访问的时候将会被再次计算;
3.声明一个可能会抛出具体异常的lazy变量,并且希望子类重写他。如果不这么做,他将会...抛出异常。

在初始化lazy变量的时候抛出异常,将会导致在下一次使用的时候,右边的值将会重新计算。

请注意,使用lazy进行多次修饰val变量的时候,将会造成一个新的危险:当第一次使用这个val变量的时候,val变量内循环会导致栈溢出。

使用早期定义

abstract class A {
  val x1: String
  val x2: String = "mom"
  println("A: " + x1 + ", " + x2)
}
class B extends {
  val x1: String = "hello"
} with A {
  println("B: " + x1 + ", " + x2)
}
class C extends {
  override val x2: String = "dad"
} with B {
  println("B: " + x1 + ", " + x2)
}
object A extends App {
  new C
}

使用早期定义会使你的代码看起来有点呆,它们的局限性包括早期代码块中他们声明跟定义的地方和他们引用的地方,并且它们书写的方式不如lazy修饰的那么方便。

使用常量值定义

abstract class A {
  val x1: String
  val x2: String = "mom"
  println("A: " + x1 + ", " + x2)
}
class B extends A{
  val x1:String="hello"
  final val x3="goodbye"
  println("B: "+x1+", "+x2)
}
class C extends B{
  override val x2:String="dad"
  println("C: "+x1+", "+x2)
}
abstract class D{
  val c:C
  val x3=c.x3
  println("D: "+c+" but "+ x3)
}
class E extends D{
  val c=new C
  println(s"E: ${c.x1},${c.x2}, and $x3...")
}
object E extends App {
  new E
}

结果输出

D: null but goodbye
A: null, null
B: hello, null
C: hello, dad
E: hello,dad, and goodbye...

有时候,你所需要的只是一个从interface处得到一个编译期常量。
常量值比早期定义的普通变量值更具限制。
本文章翻译自 Why is my abstract or overridden val null?

相关文章

  • 14.对象的初始化顺序

    对象初始化顺序 1、初始化属性 2、调用构造方法 结果:

  • 初始化顺序2

    Q:父类或者子类重写的值为什么会为空?详细的例子: 输出 只有到了初始化C的构造器的时候,x1和x2才被初始化。因...

  • Swift中的初始化方法

    1、初始化方法顺序2、Designated,Convenience和Required3、初始化方法返回nil 1、...

  • Java 继承(1)

    Java中只允许单继承 2.继承关键字:extends 3.继承初始化顺序:(1)初始化父类再初始化子类(2)先执...

  • 方法调用顺序

    方法调用顺序 1.alloc :创建对象,分配空间 2.init :初始化对象,初始化数据 3.loadView...

  • golang模块初始化

    golang的进程初始化顺序是: 1)先初始化依赖的模块,再初始化本模块 2)模块内初始化按源文件字母序初始化 3...

  • [转]Effective C++学习笔记:初始化列表中成员列出的

    类成员的默认初始化顺序是按照声明顺序进行, 如果使用初始化列表初始化成员变量, 则必须按照成员变量的声明顺序进行;...

  • C++对象模型5——对象的构造/析构

    对象的构造/析构顺序 初始化虚基类,按照继承顺序,从左到右,从最深到最浅。 初始化按照继承顺序初始化父类,如果父类...

  • Java中的继承

    继承初始化顺序 1、初始化父类再初始化子类2、先执行初始化对象中属性,再执行构造方法中的初始化 重写 1、什么是方...

  • 顺序表的操作

    顺序表的操作 这里先定义个顺序表 顺序表初始化 定义bool类型函数 initList() ,初始化成功为true...

网友评论

      本文标题:初始化顺序2

      本文链接:https://www.haomeiwen.com/subject/epvijttx.html