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

2024-01-01

UPDATE- 2014年9月17日

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

从那以后我创建了一个可验证的工作解决方案作为要点 https://gist.github.com/chaotic3quilibrium/57add1cd762eb90f6b24。该实现等待分配序数,直到after所有 JVM 类/对象初始化完成。它还有助于使用附加数据扩展/装饰每个枚举成员,同时仍然非常有效地进行(反)序列化。

我还创建了一个堆栈溢出答案 https://stackoverflow.com/a/25923651/501113它详细说明了 Scala 中使用的所有不同的枚举模式(包括我上面提到的要点中的解决方案)。


我正在全新安装类型安全 IDE http://typesafe.com/stack/downloads/scala-ide(预装了 ScalaIDE 的 Eclipse)。我使用的是 Windows 7-64 位。我使用 Scala Worksheet 取得了不同程度的成功。它已经在不到一个小时的时间内使我的机器崩溃了三次(完全重置或蓝屏死机)。因此,这可能是 Scala 工作表中的一个错误。我还不确定,也没有时间追查这个问题。但是,这个枚举问题阻止了我进行测试。

我在 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 新手,无法理解为什么会发生这种情况。任何帮助将不胜感激。

以下是 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 线程 https://stackoverflow.com/a/8620085/501113。为了简单起见,我在这里粘贴了我的版本(以防我在复制/粘贴操作期间错过了一些关键内容):

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

任何形式的指导将不胜感激。


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

这里有两个问题:一是由于初始化问题导致代码无法工作,二是您遇到了 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 into a 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 行,您在其中获得一个静态字段(这意味着 JVM 确保该字段已初始化)Values本身。但如果你去Empty you get

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初始化程序,即使它被乱序调用,也不会调用方法Empty不再。所以它奇迹般地奏效了。 (只要确保没有引入任何其他方法调用即可!)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Scala 工作表中使用自定义枚举时收到错误:java.lang.ExceptionInInitializerError 的相关文章

随机推荐