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

和如預期的輸出:

Columns really there: true
Tenant.id as Tenant_id, Tenant.name as Tenant_name
Columns really there: true
Node.id as Node_id, Node.tenantId as Node_tenantId, Node.nodeId as Node_nodeId, Node.name as Node_name

,但失敗了,如果順序顛倒:

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

,並在這種情況下,輸出是

Columns really there: true
Node.id as Node_id, Node.tenantId as Node_tenantId, Node.nodeId as Node_nodeId, Node.name as Node_name
Columns really there: false
columns List(null, null)

Exception in thread "main" java.lang.NullPointerException

使用Scala的2.10.1

一些背景資料:

  • 對象定義描述RDBMS的邏輯數據模型。
  • 表瞭解自己的列(兒童),每列都知道他的表(母公司)
  • 的外鍵列有一個可選屬性描述父表的主鍵列
  • 這種關係可以遞歸(節點表是遞歸的)
  • 表和列的單個常量是必需的。
  • 如果可能的話,我想,以避免VAR

我發現在實際需要能夠設置的語言規範(5.4)

Note that the value defined by an object definition is instantiated lazily.

節它根本就沒有。我認爲「價值」是指作爲一個整體的對象,而不是它的「價值」(屬性)。

無論如何,顯然會創建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") 
    [...] 
    } 

} 

所以我的問題是:

  • 它爲什麼失敗?
  • 在哪些狀態是列屬性(該列表存在,但元素爲空)?
  • 如何正確設置?還是應該將其按照定義的順序實現爲解決方法,因爲它具有循環/遞歸性質?

更新/溶液

基於0 __的回答是:該解決方案由聲明列屬性作爲懶惰和改變抽象VALDEF的。

至於列屬性的狀態:如果您將-Xcheckinit放入scalac的命令行選項中,則會添加其他運行時檢查。在這種情況下,出現以下錯誤:

Caused by: scala.UninitializedFieldError: Uninitialized field: RecursiveObjects.scala: 35

此錯誤以其他方式默默忽略,因此列表僅包含空值。

回答

2

val在斯卡拉初始化是可怕的,我一直都遇到這些NPE。這可能是錯綜複雜的規格,但從實際角度來看,它們確實被打破了。

我的建議是,永遠不要使用公共vals,除非它們被初始化而沒有提及其他領域或對象,但讓他們都懶惰。在這種情況下,如果你使用

lazy val columns = ... 

Tenant.ColumnsNode.Columns,預期它應該工作。


我不知道你的情況的確切初始化是什麼,但是從空,我想打電話Domain.Node這裏意味着Domain和/或Domain.Tenant尚未正確初始化。例如,如果在第二個示例前面添加一個看似虛擬的語句Domain;,它也會成功(因爲這會使Domain首先初始化)。


這裏是一個related question/answera link展示如何識別與嚴格值初始化的問題。

+0

非常感謝您的回答,非常有幫助。我已將結果添加到問題中。 – Beryllium