Использование пользовательского enum в Scala Worksheet Я получаю сообщение об ошибке: java.lang.ExceptionInInitializerError
ОБНОВЛЕНИЕ - 2014/сент./17
Оказывается, что даже решение в предыдущем обновлении (с 2013 г./фев. 19) не работает, если в качестве первой команды помещается println(Value.Player2)
; то есть ординалы по-прежнему назначаются неправильно.
С тех пор я создал проверяемое рабочее решение как Gist. Реализация ожидает назначения ординалов до тех пор, пока не завершится все инициализация класса/объекта JVM. Это также облегчает расширение/украшение каждого элемента перечисления дополнительными данными, при этом все еще очень эффективно для (де) сериализации.
Я также создал qaru.site/info/1454/..., в котором подробно описаны все различные шаблоны перечислений, используемые в Scala (включая решение в Gist, о котором я упоминал выше).
Я работаю со свежей установкой TypeSafe IDE (Eclipse с предустановленной программой ScalaIDE). Я нахожусь на Windows 7-64bit. И у меня был смешанный успех с Scala Worksheet. Он уже тяжело разбил мою машину (до полного reset или один раз на синий экран смерти) три раза менее чем за час. Таким образом, это может быть ошибкой в рабочем листе 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 происходит из qaru.site/info/1454/.... Я добавил в мою версию здесь для простоты (в случае, если я пропустил что-то критическое во время операции копирования/вставки):
package com.stack_overflow
//Copied from /info/1454/case-objects-vs-enumerations-in-scala/19656#19656
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/февраль/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 /info/1454/case-objects-vs-enumerations-in-scala/19656#19656
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
Здесь есть две проблемы: один из них заключается в том, что код не работает из-за проблем с инициализацией, а другой - с проблемами IDE. Я собираюсь решить только ошибку кода.
Проблема заключается в том, что вам нужно Empty()
запустить, прежде чем вы действительно получите доступ к Empty
, но доступ к внутренним объектам case не запускает инициализатор внешнего объекта, поскольку они только притворяются членами внутреннего объекта. (Внутри 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
или somesuch лучше (короче).
Если вы введете 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
. Так чудесно получается. (Просто убедитесь, что вы не вводите никаких других вызовов метода!)