При использовании пользовательского перечисления в рабочей таблице 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 больше Так что это чудесным образом получается. (Просто убедитесь, что вы не вводите никаких других вызовов методов!)

Другие вопросы по тегам