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