2

更新- 2014/09/17

以前の更新 (2013 年 2 月 19 日から) のソリューションでprintln(Value.Player2)さえ、最初のコマンドとして配置すると機能しないことが判明しました。つまり、序数はまだ正しく割り当てられていません。

それ以来、検証可能な実用的なソリューションを Gist として作成しました。実装は、すべての JVM クラス/オブジェクトの初期化が完了するまで、序数の割り当てを待機します。また、(逆) シリアル化に対して非常に効率的でありながら、追加のデータを使用して各列挙型メンバーを拡張/装飾することも容易になります。

また、Scala で使用されているすべての異なる列挙パターンについて詳しく説明するStackOverflow の回答も作成しました(上記の Gist のソリューションを含む)。


TypeSafe IDE (ScalaIDE がプリインストールされた Eclipse)の新規インストールで作業しています。私はWindows 7-64ビットを使用しています。そして、私は Scala Worksheet でさまざまな成功を収めました。1 時間以内に 3 回、マシンがハード クラッシュしました (フル リセットまたはブルー スクリーンに 1 回)。したがって、これは Scala Worksheet のバグである可能性があります。私はまだ確信が持てず、その問題を追跡する時間がありません. ただし、この列挙の問題により、テストが妨げられています。

Scala ワークシートで次のコードを使用しています。

package test

import com.stack_overflow.Enum

object WsTempA {
  object Value extends Enum {
    sealed abstract class Val extends EnumVal
    case object Empty   extends Val; Empty()
    case object Player1 extends Val; Player1()
    case object Player2 extends Val; Player2()
  }

  println(Value.values)
  println(Value.Empty)
}

上記は正常に動作します。ただし、最初の println をコメントアウトすると、2 行目で java.lang.ExceptionInInitializerError という例外がスローされます。そして、私はScalaの初心者で、なぜそれが起こっているのか理解できません. どんな助けでも大歓迎です。

Scala ワークシートの右側からのスタック トレースを次に示します (左側は、ここで適切に表示するために削除されています)。

java.lang.ExceptionInInitializerError
    at test.WsTempA$Value$Val.<init>(test.WsTempA.scala:7)
    at test.WsTempA$Value$Empty$.<init>(test.WsTempA.scala:8)
    at test.WsTempA$Value$Empty$.<clinit>(test.WsTempA.scala)
    at test.WsTempA$$anonfun$main$1.apply$mcV$sp(test.WsTempA.scala:14)
    at org.scalaide.worksheet.runtime.library.WorksheetSupport$$anonfun$$exe
 cute$1.apply$mcV$sp(WorksheetSupport.scala:76)
    at org.scalaide.worksheet.runtime.library.WorksheetSupport$.redirected(W
 orksheetSupport.scala:65)
    at org.scalaide.worksheet.runtime.library.WorksheetSupport$.$execute(Wor
 ksheetSupport.scala:75)
    at test.WsTempA$.main(test.WsTempA.scala:11)
    at test.WsTempA.main(test.WsTempA.scala)
 Caused by: java.lang.NullPointerException
    at test.WsTempA$Value$.<init>(test.WsTempA.scala:8)
    at test.WsTempA$Value$.<clinit>(test.WsTempA.scala)
    ... 9 more

クラス com.stack_overflow.Enum は、この StackOverflow スレッドから取得されます。簡単にするために、ここに自分のバージョンを貼り付けました(コピー/貼り付け操作中に重要なものを見逃した場合に備えて):

package com.stack_overflow

//Copied from https://stackoverflow.com/a/8620085/501113
abstract class Enum {

  type Val <: EnumVal

  protected var nextId: Int = 0

  private var values_       =       List[Val]()
  private var valuesById_   = Map[Int   ,Val]()
  private var valuesByName_ = Map[String,Val]()

  def values       = values_
  def valuesById   = valuesById_
  def valuesByName = valuesByName_

  def apply( id  : Int    ) = valuesById  .get(id  )  // Some|None
  def apply( name: String ) = valuesByName.get(name)  // Some|None

  // Base class for enum values; it registers the value with the Enum.
  protected abstract class EnumVal extends Ordered[Val] {
    val theVal = this.asInstanceOf[Val]  // only extend EnumVal to Val
    val id = nextId
    def bumpId { nextId += 1 }
    def compare( that:Val ) = this.id - that.id
    def apply() {
      if ( valuesById_.get(id) != None )
        throw new Exception( "cannot init " + this + " enum value twice" )
      bumpId
      values_ ++= List(theVal)
      valuesById_   += ( id       -> theVal )
      valuesByName_ += ( toString -> theVal )
    }
  }
}

あらゆる種類のガイダンスをいただければ幸いです。


更新- 2013 年 2 月 19 日

Rex Kerr との数サイクルの後、現在動作するコードの更新バージョンを次に示します。

package test

import com.stack_overflow.Enum

object WsTempA {
  object Value extends Enum {
    sealed abstract class Val extends EnumVal
    case object Empty   extends Val {Empty.init}   // <---changed from ...Val; Empty()
    case object Player1 extends Val {Player1.init} // <---changed from ...Val; Player1()
    case object Player2 extends Val {Player2.init} // <---changed from ...Val; Player2()
    private val init: List[Value.Val] = List(Empty, Player1, Player2) // <---added
  }

  println(Value.values)
  println(Value.Empty)
  println(Value.values)
  println(Value.Player1)
  println(Value.values)
  println(Value.Player2)
  println(Value.values)

package com.stack_overflow

//Copied from https://stackoverflow.com/a/8620085/501113
abstract class Enum {

  type Val <: EnumVal

  protected var nextId: Int = 0

  private var values_       =       List[Val]()
  private var valuesById_   = Map[Int   ,Val]()
  private var valuesByName_ = Map[String,Val]()

  def values       = values_
  def valuesById   = valuesById_
  def valuesByName = valuesByName_

  def apply( id  : Int    ) = valuesById  .get(id  )  // Some|None
  def apply( name: String ) = valuesByName.get(name)  // Some|None

  // Base class for enum values; it registers the value with the Enum.
  protected abstract class EnumVal extends Ordered[Val] {
    val theVal = this.asInstanceOf[Val]  // only extend EnumVal to Val
    val id = nextId
    def bumpId { nextId += 1 }
    def compare(that: Val ) = this.id - that.id
    def init() {   // <--------------------------changed name from apply
      if ( valuesById_.get(id) != None )
        throw new Exception( "cannot init " + this + " enum value twice" )
      bumpId
      values_ ++= List(theVal)
      valuesById_   += ( id       -> theVal )
      valuesByName_ += ( toString -> theVal )
    }
  }
}
4

1 に答える 1

2

ここには 2 つの問題があります。1 つは初期化の問題が原因でコードが機能しないこと、もう 1 つは IDE に問題があることです。コードの失敗のみに対処します。

問題は、Empty()実際に にアクセスする前に実行する必要があることですがEmpty、内側のケース オブジェクトにアクセスしても、内側のオブジェクトのメンバーのふりをしているだけなので、外側のオブジェクトで初期化子が実行されません。Value(内部に を保持する変数はありませんEmpty。)

apply()のイニシャライザの一部としてメソッドを実行することで、この問題を回避できますEmpty

object Value extends Enum {
  sealed abstract class Val extends EnumVal
  case object Empty   extends Val { apply() }
  case object Player1 extends Val { apply() }
  case object Player2 extends Val { apply() }
}

これで、初期化エラーが解消されるはずです。(このようにしなければならないので、apply()実際には名前の選択が悪いことをお勧めします。おそらくset、または何かがより良い(短い)でしょう。

printlnをメソッドに貼り付けると、main印刷用のバイトコードは次のようになりますValue.values

public void main(java.lang.String[]);
  Code:
   0:   getstatic   #19; //Field scala/Predef$.MODULE$:Lscala/Predef$;
   3:   getstatic   #24; //Field WsTempA$Value$.MODULE$:LWsTempA$Value$;
   6:   invokevirtual   #30; //Method Enum.values:()Lscala/collection/immutable/List;
   9:   invokevirtual   #34; //Method scala/Predef$.println:(Ljava/lang/Object;)V
   12:  return

3 行目で、それ自体の static フィールド (JVM がフィールドの初期化を保証することを意味します) を取得していることに注意してくださいValues。しかし、あなたが行くなら、Emptyあなたは得る

public void main(java.lang.String[]);
  Code:
   0:   getstatic   #19; //Field scala/Predef$.MODULE$:Lscala/Predef$;
   3:   getstatic   #24; //Field WsTempA$Value$Empty$.MODULE$:LWsTempA$Value$Empty$;
   6:   invokevirtual   #28; //Method scala/Predef$.println:(Ljava/lang/Object;)V
   9:   return

ここで、3 行目は内部オブジェクトではなくValues、内部オブジェクトを参照しています。つまり、内部オブジェクトの初期化子が最初に呼び出され、次に外部オブジェクトの初期化子が呼び出され、内部オブジェクトの初期化子が実行されるはずであることがわかります (ただし、実際には実行されていません)。 ...そして、その上でメソッドを呼び出すと...ブーム。

applyイニシャライザの内部に配置すると、イニシャライザは順不同で呼び出されてもメソッドを呼び出さないため、Empty保存されます。それで奇跡的にうまくいく。(他のメソッド呼び出しを導入していないことを確認してください!)ValueEmpty

于 2013-02-19T00:51:02.570 に答える