更新 - 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 )
}
}
}
答案 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}上的方法}} 不再。所以它奇迹般地成功了。 (只是确保你没有引入任何其他方法调用!)