12

ファイルからプロパティをロードする次のコードがあります。

class Config {
  val properties: Properties = {
    val p = new Properties()
    p.load(Thread.currentThread().getContextClassLoader.getResourceAsStream("props"))
    p
  }

  val forumId = properties.get("forum_id")
}

これは正常に機能しているようです。

次のように、の初期化propertiesを別のvalに移動してみましたloadedProperties

class Config {
  val properties: Properties = loadedProps
  val forumId = properties.get("forum_id")

  private val loadedProps = {
    val p = new Properties()
    p.load(Thread.currentThread().getContextClassLoader.getResourceAsStream("props"))
    p 
   }

}

しかし、それは機能しません!(propertiesではnullですproperties.get("forum_id"))。

なぜそうなるのでしょうか?loadedProps参照されたときに評価されませpropertiesんか?

第二に、これは重要な処理を必要とする変数を初期化するための良い方法ですか?finalJavaでは、フィールドを宣言し、コンストラクターで初期化関連の操作を行います。

Scalaにこのシナリオのパターンはありますか?

ありがとうございました!

4

3 に答える 3

21

Vals are initialized in the order they are declared (well, precisely, non-lazy vals are), so properties is getting initialized before loadedProps. Or in other words, loadedProps is still null when propertiesis getting initialized. The simplest solution here is to define loadedProps before properties:

class Config {
  private val loadedProps = {
    val p = new Properties()
    p.load(Thread.currentThread().getContextClassLoader.getResourceAsStream("props"))
    p 
  }
  val properties: Properties = loadedProps
  val forumId = properties.get("forum_id")
}

You could also make loadedProps lazy, meaning that it will be initialized on its first access:

class Config {
  val properties: Properties = loadedProps
  val forumId = properties.get("forum_id")

  private lazy val loadedProps = {
    val p = new Properties()
    p.load(Thread.currentThread().getContextClassLoader.getResourceAsStream("props"))
    p 
  }
}

Using lazy val has the advantage that your code is more robust to refactoring, as merely changing the declaration order of your vals won't break your code.

Also in this particular occurence, you can just turn loadedProps into a def (as suggested by @NIA) as it is only used once anyway.

于 2013-01-28T18:42:33.380 に答える
5

loadedPropsここでは、次のように置き換えるだけで関数に変換できるとval思いますdef

private def loadedProps = {
  // Tons of code
}

この場合、あなたはそれを呼び出すときにそれが呼び出されることを確信しています。

しかし、それがこの場合のパターンであるかどうかはわかりません。

于 2013-01-28T17:54:53.940 に答える
4

もう少し説明を加えた追加:

あなたのpropertiesフィールドは、ここのフィールドよりも早く初期化さloadedPropsれます。null初期化前のフィールドの値です-それが取得する理由です。def一部のフィールドにアクセスするのではなく、単なるメソッド呼び出しである場合は、すべて問題ありません (メソッドのコードは数回呼び出される可能性があるため、ここでは初期化はありません) 。http://docs.scala-lang.org/tutorials/FAQ/initialization-order.htmlを参照してください。あなたはそれを使用するdefか、それlazy valを修正することができます

なぜdefそんなに違うのですか?これdefは、複数回呼び出される可能性があるためですが、 val- 1 回だけです (そのため、実際には最初で 1 回だけの呼び出しが fileld の初期化です)。

lazy val呼び出したときだけ初期化できるので、それも役立ちます。

何が起こっているかの別のより簡単な例:

scala> class A {val a = b; val b = 5}
<console>:7: warning: Reference to uninitialized value b
       class A {val a = b; val b = 5}
                        ^
defined class A

scala> (new A).a
res2: Int = 0 //null

より一般的に言えば、理論的には、scala はフィールド間の依存関係グラフ (どのフィールドが他のフィールドを必要とするか) を分析し、最終ノードから初期化を開始できます。しかし実際には、すべてのモジュールは個別にコンパイルされ、コンパイラーはそれらの依存関係さえ知らない可能性があります (Java を呼び出す Scala を呼び出す Java でさえある可能性があります)。そのため、順次初期化を行うだけです。

そのため、単純なループを検出することさえできませんでした:

scala> class A {val a: Int = b; val b: Int = a}
<console>:7: warning: Reference to uninitialized value b
       class A {val a: Int = b; val b: Int = a}
                             ^
defined class A

scala> (new A).a
res4: Int = 0

scala> class A {lazy val a: Int = b; lazy val b: Int = a}
defined class A

scala> (new A).a
java.lang.StackOverflowError

実際、このような (1 つのモジュール内の) ループは、理論的には別のビルドで検出できますが、かなり明白であるため、あまり役に立ちません。

于 2015-04-16T12:27:18.883 に答える