2

以下代码

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
真正存在的列:假
列 List(null, null)

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

使用 Scala 2.10.1

一些背景资料:

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

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

请注意,对象定义定义的值是惰性实例化的。

这实际上是能够设置它所必需的。我假设“价值”是指整个对象,与其“价值”(属性)相反。

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

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

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 属性声明为惰性并将抽象 val更改为def

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

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

否则此错误会被静默忽略,因此列表仅包含空值。

4

1 回答 1

2

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

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

lazy val columns = ...

Tenant.Columns和中Node.Columns,它应该按预期工作。


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


这是一个相关的问题/答案,其中的链接显示了如何通过严格的值初始化来识别问题。

于 2013-05-27T14:48:58.747 回答