【发布时间】: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