在 Scala 中的特征构造函数之前调用 class 构造函数

Call class contructor before trait's constructor in scala

我有这个场景

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 抛出空指针异常,因为 class B 的构造函数还没有被调用,因此位置没有被初始化。 如果我将 class B 的声明更改为

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

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

目前我已将位置声明移至不同的特征,并且我的程序按预期工作,但我想尽可能避免不必要的特征。

您尝试的解决方案是所谓的早期初始化。你不能在其中调用 someMethod,因为:

  • 早期初始化程序只能包含 val 个定义
  • 包含someMethod的trait D混合在之后的早期初始化器中,所以还不能使用

但无论如何,在确定初始化顺序时,早期初始化程序应该被视为最后的手段。在回到它之前,您应该首先尝试一些不那么棘手的解决方案:

  1. 与其定义或覆盖 trait 中的 val,不如尝试将其设为构造函数参数。 vals 的构造函数参数在调用任何构造函数代码之前被初始化。这里可以引入一个中间摘要class:

    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()
    }