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