在scala中的trait构造函数之前调用类构造函数

时间:2017-02-05 13:38:59

标签: scala traits

我有这个场景

trait D {
    def someMethod(): Unit = {}
  }

  trait C {
    val locations: Seq[Int]

    someSomethingWithLocations() // calling this in constructor

    def someSomethingWithLocations(): Unit = {
      print(locations.length)
    }
  }

  class B extends D with C {
    override val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
    someMethod()
  }

  def main(args: Array[String]): Unit = {
    val b = new B
  }

当我运行此代码时,someSomethingWithLocations抛出空指针异常,因为类B的构造函数尚未被调用,因此位置未初始化。 如果我将B类的声明改为

      class B extends{
        override val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
        someMethod()
      } with D with C 

编译器抱怨找不到someMethod()。我该如何解决这个问题?

现在我已经将位置声明移到了不同​​的特性,我的程序按预期工作,但我想尽可能避免不必要的特性。

1 个答案:

答案 0 :(得分:3)

您尝试的解决方案是所谓的早期初始化程序。您无法在其中调用someMethod,因为:

  • 早期初始值设定项只能包含val个定义
  • 包含D的特征someMethod在早期初始化程序之后的中混合,因此无法使用

但无论如何,在修复初始化顺序时,应将早期初始化程序视为最后的手段。在回归之前,你应该首先尝试一些不那么糟糕的解决方案:

  1. 不要从特征中定义或覆盖val,而是尝试将其作为构造函数参数。在调用任何构造函数代码之前,将初始化val的构造函数参数。在这里,您可以通过引入中间抽象类来实现:

    abstract class AbstractB(val locations: Seq[Int])
    class B extends AbstractB(1 :: 2 :: 3 :: Nil) with D with C {
      someMethod()
    }
    
  2. val成为lazy val

    class B extends D with C {
      override lazy val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
      someMethod()
    }
    

    这种方式locations不会在class B的构造函数中初始化,而只是初次访问,在您的情况下将是trait C的构造函数(请注意,在这种情况下, lazy关键字使字段早于早于,而不是以后,这是对lazy val s的常见直觉。

    lazy val似乎是一个简单易用的解决方案,但我建议首先尝试使用构造函数参数。这是因为lazy val本身可能会访问另一个val,这可能尚未在此时初始化。这样问题就会升级到其他val,最后您可能会发现自己必须将所有问题都声明为lazy

  3. 如果您仍想使用早期初始化程序,则需要将方法调用移到其外部并将其放入构造函数中:

    class B extends {
      override val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
    } with D with C {
      someMethod()
    }