【问题标题】:Initialization order of values in objects: How to setup cyclic/recursive objects properly?对象中值的初始化顺序:如何正确设置循环/递归对象?
【发布时间】:2013-05-27 13:00:43
【问题描述】:

以下代码

abstract class Table(val name: String) {
  val columns: List[Column]

  def getAliasColumns: String = {
    val reallyThere = columns.forall(c => c != null)
    println("Columns really there: " + reallyThere)
    if (reallyThere == false) {
      println(" columns " + columns)
    }

    columns.map(c => s"${c.table.name}.${c.name} as ${c.table.name}_${c.name}")
      .mkString(", ")
  }
}

 

class Column(val table: Table, val name: String, val foreignKey: Option[Column])

object Column {
  def apply(table: Table, name: String): Column = {
    new Column(table, name, foreignKey = None)
  }

  def apply(table: Table, name: String, fk: Column): Column = {
    new Column(table, name, Some(fk))
  }
}

 

object Domain {
  object Tenant extends Table("Tenant") {
    object Columns {
      // Primary key
      val Id = Column(Tenant, "id")
      // Just a name
      val Name = Column(Tenant, "name")
    }

    val columns = List(Columns.Id, Columns.Name)
  }

  object Node extends Table("Node") {
    object Columns {
      // Primary key
      val Id = Column(Node, "id")

      // Foreign key to another table
      val TenantId = Column(Node, "tenantId", Tenant.Columns.Id)

      // Foreign key to itself
      val NodeId = Column(Node, "nodeId", Id)

      // Just a name
      val Name = Column(Node, "name")
    }

    val columns = List(Columns.Id, Columns.TenantId, 
      Columns.NodeId, Columns.Name)
  }

  val tables = List(Tenant, Node)
}

有效,如果访问信息的顺序是:

object RecursiveObjects extends App {
  Domain.tables.foreach(t => println(t.getAliasColumns))
  println(Domain.Node.getAliasColumns)
}

输出如预期:

确实存在列:true
Tenant.id 为 Tenant_id,Tenant.name 为 Tenant_name
列确实存在:true
Node.id 作为 Node_id,Node.tenantId 作为 Node_tenantId,Node.nodeId 作为 Node_nodeId,Node.name 作为 Node_name

但如果顺序颠倒,它就会失败:

object RecursiveObjects extends App {
  println(Domain.Node.getAliasColumns)
  Domain.tables.foreach(t => println(t.getAliasColumns))
}

在这种情况下,输出是

确实存在列:true
Node.id 作为 Node_id,Node.tenantId 作为 Node_tenantId,Node.nodeId 作为 Node_nodeId,Node.name 作为 Node_name
列确实存在:false
列列表(空,空)

线程“main”中的异常 java.lang.NullPointerException

使用 Scala 2.10.1

一些背景资料:

  • 对象定义描述了 RDBMS 的逻辑数据模型。
  • 表知道他们的列(子),每列知道他的表(父)
  • 外键列具有描述父表中主键列的可选属性
  • 这种关系可以是递归的(节点表是递归的)
  • 表和列的单独常量是必需的。
  • 如果可能,我想避免 var

我在语言规范 (5.4) 中找到了一个部分

请注意,由对象定义定义的值是延迟实例化的。

这实际上是能够设置它所必需的。我假设“价值”是指作为一个整体的对象,而不是它的“价值”(属性)。

无论如何,columns 属性的实例显然已创建,但其元素尚未“物化”,这在第二次运行的输出中可见。

我尝试使用早期定义来解决它,但在这种情况下,编译器会抱怨涉及对象的非法循环引用,因此无法编译:

object Node extends {
  val columns = List(Domain.Node.Columns.Id, Domain.Node.Columns.TenantId,
                     Domain.Node.Columns.NodeId, Domain.Node.Columns.Name)
} with Table("Node") {
  object Columns {
    // Primary key
    val Id = Column(Node, "id")
    [...]
  }

}

所以我的问题是:

  • 为什么会失败?
  • columns 属性处于哪种状态(列表存在,但元素为空)?
  • 如何正确设置?还是由于它的循环/递归性质,我应该按照定义的顺序将其具体化为一种解决方法?

更新/解决方案

基于 0__ 的回答:解决方案包括将 columns 属性声明为 lazy 并将 abstract val 更改为 def。 p>

关于 columns 属性的状态:如果你把 -Xcheckinit 放到 scalac 的命令行选项中,会增加额外的运行时检查。在这种情况下会出现以下错误:

引起:scala.UninitializedFieldError:未初始化字段:RecursiveObjects.scala:35

这个错误会被忽略,所以列表只包含空值。

【问题讨论】:

    标签: scala lazy-initialization recursive-datastructures cyclic-reference


    【解决方案1】:

    val 在 Scala 中的初始化很糟糕,我一直遇到这些 NPE。从规范的复杂性来看,这可能一切都很好,但从实际的角度来看,它们确实被打破了。

    我的建议是永远不要使用公共 val,除非它们被初始化没有引用其他字段或对象,但要让它们都变得懒惰。在这种情况下,如果您使用

    lazy val columns = ...
    

    Tenant.ColumnsNode.Columns 中,它应该可以按预期工作。


    我不确定在您的情况下的确切初始化是什么,但从空值来看,我认为调用Domain.Node 在这里暗示Domain 和/或Domain.Tenant 尚未正确初始化。例如,如果您在第二个示例前面添加一个看似虚拟的语句 Domain;,它也会成功(因为这会使 Domain 首先初始化)。


    这是一个related question/answera link,展示了如何识别严格值初始化的问题。

    【讨论】:

    • 非常感谢您的回答,非常有帮助。我已将我的结果添加到问题中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-06
    • 1970-01-01
    • 2019-08-07
    • 1970-01-01
    相关资源
    最近更新 更多