Scala与Java有些许不同点,但是主要机制遵循了Java虚拟机。 Int默认值 0,对象类型默认值null,以此类推。

Java 类加载机制顺序是,加载 → 链接(验证+准备+解析)→ 初始化(使用前的准备) 静态变量在准备阶段被初始化为默认值,初始化时才被设置为用户传入的值,而final的静态变量在准备阶段就被设置为用户传入的值。

scala中object中属性都是静态的,类似Java静态属性,但是final仅起到了不能被覆盖的作用,并没有让变量/常量在准备阶段就被初始化为实际值,这是因为object继承了App特质,被认为是主函数,声明的静态量在主函数执行时才会被初始化。scala final也是可以修饰var,而var又是众所周知的变量,可见final对变量常量的赋值是无影响的,而是不能被覆盖,如果有object继承该object,那么这个值是不能被重写的。(object不能继承object,能继承特质、类,所以在此final是无用的) 这里lazy这个懒加载起到了类似Java final的作用,因为lazy是懒求值,只有在使用时才会获取实际值,而这时类肯定是被加载完成了,所以才能拿到真正的实际值,不受App特质影响。

现在在object中有以下6种变量/常量

   //lazy var 是不允许的,
  //常量,不可被覆盖,不可修改,懒值
  private final lazy val default1 = Seq(1, 2, 3, 4)

  //变量,可被修改不可被覆盖
  private final var default2 = Seq(1, 2, 3, 4)

  //常量,不可变,懒值
  private lazy val default3 = Seq(1, 2, 3, 4)

  //常量,不可变,不可覆盖
  private final val default4 = Seq(1, 2, 3, 4)

  //普通常量
  private val default5 = Seq(1, 2, 3, 4)

  //普通变量
  private var default6 = Seq(1, 2, 3, 4)

在object中继承 extends App 并执行

很显然是可以打印出值,完整代码如下

/**
 * @author 梦境迷离
 * @version 1.0, 2019-06-20
 */
object MainObject1 extends App {

  //lazy var 是不允许的,
  //常量,不可被覆盖,不可修改,懒值
  private final lazy val default1 = Seq(1, 2, 3, 4)
  println("final lazy val 初始化 =>>>>> " + default1)

  //变量,可被修改不可被覆盖
  private final var default2 = Seq(1, 2, 3, 4)
  println("final var 初始化 =>>>>> " + default2)

  //常量,不可变,懒值
  private lazy val default3 = Seq(1, 2, 3, 4)
  println("lazy val 初始化 =>>>>> " + default3)

  //常量,不可变,不可覆盖
  private final val default4 = Seq(1, 2, 3, 4)
  println("final val 初始化 =>>>>> " + default4)

  //普通常量
  private val default5 = Seq(1, 2, 3, 4)
  println("val 初始化 =>>>>> " + default5)

  //普通变量
  private var default6 = Seq(1, 2, 3, 4)
  println("var 初始化 =>>>>> " + default6)
}

输出,很正常结果

final lazy val 初始化 =>>>>> List(1, 2, 3, 4)
final var 初始化 =>>>>> List(1, 2, 3, 4)
lazy val 初始化 =>>>>> List(1, 2, 3, 4)
final val 初始化 =>>>>> List(1, 2, 3, 4)
val 初始化 =>>>>> List(1, 2, 3, 4)
var 初始化 =>>>>> List(1, 2, 3, 4)

将打印函数写在方法中

代码如下

 def getData() = {
    println("方法调用1 =>>>>>> " + default1)
    println("方法调用2 =>>>>>> " + default2)
    println("方法调用3 =>>>>>> " + default3)
    println("方法调用4 =>>>>>> " + default4)
    println("方法调用5 =>>>>>> " + default5)
    println("方法调用6 =>>>>>> " + default6)
  }

完整代码

package cn.edu.jxnu.other

/**
 * @author 梦境迷离
 * @version 1.0, 2019-06-20
 */
object MainObject1 extends App {

  //lazy var 是不允许的,
  //常量,不可被覆盖,不可修改,懒值
  private final lazy val default1 = Seq(1, 2, 3, 4)
  println("final lazy val 初始化 =>>>>> " + default1)

  //变量,可被修改不可被覆盖
  private final var default2 = Seq(1, 2, 3, 4)
  println("final var 初始化 =>>>>> " + default2)

  //常量,不可变,懒值
  private lazy val default3 = Seq(1, 2, 3, 4)
  println("lazy val 初始化 =>>>>> " + default3)

  //常量,不可变,不可覆盖
  private final val default4 = Seq(1, 2, 3, 4)
  println("final val 初始化 =>>>>> " + default4)

  //普通常量
  private val default5 = Seq(1, 2, 3, 4)
  println("val 初始化 =>>>>> " + default5)

  //普通变量
  private var default6 = Seq(1, 2, 3, 4)
  println("var 初始化 =>>>>> " + default6)

  def getData() = {
    println("方法调用1 =>>>>>> " + default1)
    println("方法调用2 =>>>>>> " + default2)
    println("方法调用3 =>>>>>> " + default3)
    println("方法调用4 =>>>>>> " + default4)
    println("方法调用5 =>>>>>> " + default5)
    println("方法调用6 =>>>>>> " + default6)
  }
  //调用
  getData
}

结果如下,也是正常的

final lazy val 初始化 =>>>>> List(1, 2, 3, 4)
final var 初始化 =>>>>> List(1, 2, 3, 4)
lazy val 初始化 =>>>>> List(1, 2, 3, 4)
final val 初始化 =>>>>> List(1, 2, 3, 4)
val 初始化 =>>>>> List(1, 2, 3, 4)
var 初始化 =>>>>> List(1, 2, 3, 4)
方法调用1 =>>>>>> List(1, 2, 3, 4)
方法调用2 =>>>>>> List(1, 2, 3, 4)
方法调用3 =>>>>>> List(1, 2, 3, 4)
方法调用4 =>>>>>> List(1, 2, 3, 4)
方法调用5 =>>>>>> List(1, 2, 3, 4)
方法调用6 =>>>>>> List(1, 2, 3, 4)

在其他object中调用getData方法

代码如下

/**
 * 测试 初始化顺序
 *
 * @author 梦境迷离
 * @version 1.0, 2019-06-20
 */
object MainObject2 extends App {

  MainObject1.getData

}

执行上述代码,得到结果

方法调用1 =>>>>>> List(1, 2, 3, 4)
方法调用2 =>>>>>> null
方法调用3 =>>>>>> List(1, 2, 3, 4)
方法调用4 =>>>>>> null
方法调用5 =>>>>>> null
方法调用6 =>>>>>> null

总结

如果被调用方继承了App特质,则相当于Java main方法,如果不执行main,则在main的值不会被初始化为实际值,只会被初始化为默认值(例如Int被初始化为0,而不是你设置的值),而去掉MainObject1继承的特质则会得到正确的值。

  private final lazy val default1 = Seq(1, 2, 3, 4)
  private final var default2 = Seq(1, 2, 3, 4)//null
  private lazy val default3 = Seq(1, 2, 3, 4)
  private final val default4 = Seq(1, 2, 3, 4)//null
  private val default5 = Seq(1, 2, 3, 4)//null
  private var default6 = Seq(1, 2, 3, 4)//null

去掉MainObject1的App特质,同样在MainObject2中调用getData方法结果如下

final lazy val 初始化 =>>>>> List(1, 2, 3, 4)
final var 初始化 =>>>>> List(1, 2, 3, 4)
lazy val 初始化 =>>>>> List(1, 2, 3, 4)
final val 初始化 =>>>>> List(1, 2, 3, 4)
val 初始化 =>>>>> List(1, 2, 3, 4)
var 初始化 =>>>>> List(1, 2, 3, 4)
方法调用1 =>>>>>> List(1, 2, 3, 4)
方法调用2 =>>>>>> List(1, 2, 3, 4)
方法调用3 =>>>>>> List(1, 2, 3, 4)
方法调用4 =>>>>>> List(1, 2, 3, 4)
方法调用5 =>>>>>> List(1, 2, 3, 4)
方法调用6 =>>>>>> List(1, 2, 3, 4)
方法调用1 =>>>>>> List(1, 2, 3, 4)
方法调用2 =>>>>>> List(1, 2, 3, 4)
方法调用3 =>>>>>> List(1, 2, 3, 4)
方法调用4 =>>>>>> List(1, 2, 3, 4)
方法调用5 =>>>>>> List(1, 2, 3, 4)
方法调用6 =>>>>>> List(1, 2, 3, 4)

所以在平时写object时,运行时一定要去掉所有object中的extends App,我这次遇到这种情况主要是测试完object后忘记删除App导致后续遇到 config读取默认值为null。