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

出力は期待どおりです。

本当にそこにある列: Tenant_id としての真の
Tenant.id、Tenant_name としての Tenant.name

ただし、順序が逆の場合は失敗します。

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

この場合、出力は

実際に存在する列: Node_id として真の
Node.id、Node_tenantId として Node.tenantId、Node_nodeId として Node.nodeId、Node_name として Node.name
列が実際に存在: false
列 List(null, null)

スレッド「メイン」での例外 java.lang.NullPointerException

Scala 2.10.1 を使用

背景情報:

  • オブジェクト定義は、RDBMS の論理データ モデルを記述します。
  • テーブルはその列 (子) を認識し、各列は自分のテーブル (親) を認識します
  • 外部キー列には、親テーブルの主キー列を説明するオプションのプロパティがあります
  • この関係は再帰的です (ノード テーブルは再帰的です)。
  • テーブルと列の個々の定数が必要です。
  • できればvarは避けたい

言語仕様のセクションを見つけました (5.4)

オブジェクト定義によって定義された値は遅延してインスタンス化されることに注意してください。

これは、実際にセットアップできるようにするために必要です。「値」(プロパティ)とは対照的に、「値」はオブジェクト全体を意味すると思います。

とにかく、列プロパティのインスタンスは明らかに作成されていますが、その要素はまだ「具体化」されていません。これは、2 回目の実行の出力に表示されます。

初期の定義を使用して解決しようとしましたが、この場合、コンパイラは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")
    [...]
  }

}

だから私の質問は:

  • なぜ失敗するのですか?
  • 列プロパティはどの状態ですか (リストは存在しますが、要素は null です)。
  • これを正しく設定するにはどうすればよいですか?または、循環/再帰的な性質のため、回避策として定義された順序で具体化する必要がありますか?

更新/解決策

0__ の回答に基づく: 解決策は、 columns プロパティを遅延として宣言し、抽象 valdefに変更することで構成されます。

columns プロパティの状態について: scalac のコマンド ライン オプションに-Xcheckinitを指定すると、追加の実行時チェックが追加されます。この場合、次のエラーが表示されます。

原因: scala.UninitializedFieldError: 未初期化フィールド: RecursiveObjects.scala: 35

それ以外の場合、このエラーは黙って無視されるため、リストには null のみが含まれます。

4

1 に答える 1

2

valScala での初期化はひどいものです。私はこれらの NPE にいつも出くわします。仕様の複雑さからすれば問題ないのかもしれませんが、実用的な観点から見ると、それらは本当に壊れています。

私のアドバイスは、他のフィールドやオブジェクトを参照せずに初期化されていない限り、public vals を使用しないことですが、それらはすべて遅延させます。この場合、

lazy val columns = ...

と でTenant.ColumnsNode.Columns期待どおりに動作するはずです。


あなたの場合の正確な初期化が何であるかはわかりませんが、ヌルから、呼び出しはここで、および/またはまだ適切に初期化されていないことをDomain.Node意味すると思います。たとえば、2 番目の例の前に一見ダミーのステートメントを追加すると、それも成功します (最初に初期化するため)。DomainDomain.TenantDomain;Domain


これは、厳密な値の初期化で問題を特定する方法を示すリンク付きの関連する質問/回答です。

于 2013-05-27T14:48:58.747 に答える