2

更新- 2014 年 9 月 17 日

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

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

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


我正在使用全新安装的TypeSafe 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 线程。为了简单起见,我在此处粘贴了我的版本(以防我在复制/粘贴操作期间遗漏了一些关键内容):

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/Feb/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

这里有两个问题:一个是由于初始化问题导致代码无法工作,另一个是您遇到了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 行,您在其中获得了一个静态字段(这意味着 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,你会被保存,因为Value初始化器,即使它被乱序调用,也不会再调用方法了Empty。所以奇迹般地成功了。(只要确保你没有引入任何其他方法调用!)

于 2013-02-19T00:51:02.570 回答