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

在trait'之前调用类构造函数;scala中的s构造函数,scala,traits,Scala,Traits,我有这种情况 trait D { def someMethod(): Unit = {} } trait C { val locations: Seq[Int] someSomethingWithLocations() // calling this in constructor def someSomethingWithLocations(): Unit = { print(locations.length) } }

我有这种情况

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
  }
当我运行这个代码
somesomethinghithlocations
时,抛出空指针异常,因为还没有调用类B的构造函数,所以位置没有初始化。 如果我把B类的声明改为

      class B extends{
        override val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
        someMethod()
      } with D with C 
编译器抱怨找不到someMethod()。我如何解决这个问题


现在,我已经将位置声明移到了一个不同的特性,我的程序按预期工作,但如果可能的话,我希望避免不必要的特性。

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

  • 早期初始值设定项只能包含
    val
    定义
  • 包含
    someMethod
    的trait
    D
    是在早期初始值设定项之后混合的,因此它还不能使用
但无论如何,在确定初始化顺序时,应将早期初始化者视为最后手段。在回到正题之前,您应该首先尝试一些不太老套的解决方案:

  • 与其从trait定义或重写
    val
    ,不如尝试将其作为构造函数参数。调用任何构造函数代码之前,将初始化
    val
    s的构造函数参数。在这里,您可以通过引入一个中间抽象类来实现:

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

    class B extends D with C {
      override lazy val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
      someMethod()
    }
    
    这样一来,
    位置
    将不会在
    B类
    的构造函数中初始化,而只是在首次访问时初始化,在您的情况下,这将是
    特质C的构造函数
    (请注意,在这种情况下,
    lazy
    关键字使字段比正常情况下更早而不是更晚初始化,这是关于
    lazy val
    s的常见直觉)

    lazy val
    似乎是一个简单易行的解决方案,但如果可能的话,我建议首先尝试构造函数参数。这是因为
    lazy val
    本身可能会访问另一个
    val
    ,而此时可能尚未初始化。这样,问题会升级到其他
    val
    上,最终您可能会遇到问题发现自己不得不将它们全部声明为
    lazy

  • 如果仍要使用早期初始值设定项,则需要将方法调用移到其外部,并将其放入构造函数中:

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

  • 您尝试的解决方案称为早期初始值设定项。您不能在其中调用
    someMethod
    ,因为:

    • 早期初始值设定项只能包含
      val
      定义
    • 包含
      someMethod
      的trait
      D
      是在早期初始值设定项之后混合的,因此它还不能使用
    但无论如何,在确定初始化顺序时,应将早期初始值设定者视为最后手段。在重新开始之前,应首先尝试一些不太老套的解决方案:

  • 与其从trait中定义或重写
    val
    ,不如尝试将其设置为构造函数参数。在调用任何构造函数代码之前,将初始化
    val
    s构造函数参数。在这里,您可以通过引入中间抽象类来完成此操作:

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

    class B extends D with C {
      override lazy val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
      someMethod()
    }
    
    这样一来,
    位置
    将不会在
    B类
    的构造函数中初始化,而只是在首次访问时初始化,在您的情况下,这将是
    特质C的构造函数
    (请注意,在这种情况下,
    lazy
    关键字使字段比正常情况下更早而不是更晚初始化,这是关于
    lazy val
    s的常见直觉)

    lazy val
    似乎是一个简单易行的解决方案,但如果可能的话,我建议首先尝试构造函数参数。这是因为
    lazy val
    本身可能会访问另一个
    val
    ,而此时可能尚未初始化。这样,问题会升级到其他
    val
    上,最终您可能会遇到问题发现自己不得不将它们全部声明为
    lazy

  • 如果仍要使用早期初始值设定项,则需要将方法调用移到其外部,并将其放入构造函数中:

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