在Scala工作表中使用自定义枚举我收到一个错误:java.lang.ExceptionInInitializerError

时间:2013-02-18 23:26:26

标签: scala enums scala-ide

更新 - 2014 / Sep / 17

事实证明,如果将println(Value.Player2)作为第一个命令放置,即使先前更新中的解决方案(从2013/2月19日)也无法正常工作;即,序数仍然分配不正确。

我已经创建了verifiable working solution as a Gist。在所有JVM类/对象初始化完成之后,实现等待分配序数直到。它还有助于使用附加数据扩展/装饰每个枚举成员,同时仍然非常有效地进行(反)序列化。

我还创建了一个StackOverflow answer,详细说明了Scala中使用的所有不同的枚举模式(包括我在上面提到的Gist中的解决方案)。


我正在使用全新安装的TypeSafe IDE(预装了ScalaIDE的Eclipse)。我在Windows 7-64bit上。我在Scala工作表方面取得了不同的成功。在不到一个小时的时间里,它已经很难将我的机器(完全重置或一次到蓝屏死机)三次撞毁。因此,这可能是Scala工作表中的错误。我还不确定,也没有时间去追查这个问题。但是,这个enum问题阻止了我进行测试。

我在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,则第二行会抛出异常:java.lang.ExceptionInInitializerError。我只是一个斯卡拉新手,不明白它为什么会发生。任何帮助都将深表感谢。

这是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类来自this StackOverflow thread。为了简单起见,我在这里粘贴了我的版本(如果我在复制/粘贴操作期间错过了一些关键的东西):

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

1 个答案:

答案 0 :(得分:2)

这里有两个问题:一个是代码由于初始化问题而无法工作,另一个是您遇到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行,您将获得Values本身的静态字段(这意味着JVM确保该字段已初始化)。但是,如果你去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初始值设定项中,则会保存,因为Value初始值设定项(即使它不按顺序调用)也不会调用{{1}上的方法}} 不再。所以它奇迹般地成功了。 (只是确保你没有引入任何其他方法调用!)