Типы событий "Свидетельство" - "Внешняя ссылка в этом типе теста не может быть проверена во время выполнения"

Я реализую компонент Swing, и я хочу преодолеть неотображение #### Reactor. Поэтому я думал, что это сработает:

trait Foo[A] extends scala.swing.Publisher {
  final case class Bar(parent: Vector[A], children: A*) extends scala.swing.event.Event
}

trait Test {
  val foo: Foo[Int]

  foo.reactions += {
    case foo.Bar(parent, children) => {
      println(parent.sum - children)
    }
  }
}

К сожалению, это дает мне два предупреждения о компиляторе:

The outer reference in this type test cannot be checked at run time.
  final case class Bar(parent: Vector[A], children: A*) extends scala.swing.event.Event
                   ^
The outer reference in this type test cannot be checked at run time.
    case foo.Bar(parent, children) => {
                ^

Следует ли игнорировать эти предупреждения? Могу ли я их подавить? Должен ли я изменить дизайн?

Ответы

Ответ 1

В Scala внутренние классы являются "зависимыми от пути".

Я буду использовать ваш код в качестве примера. Если у вас есть два Foo[Int] s, называемые foo и bar, то foo.Bar - это другой тип bar.Bar. Обратите внимание, что это отличается от идеи Java внутренних классов, где foo.Bar и bar.Bar являются одним и тем же типом.

В любом случае JVM не поддерживает внутренние классы напрямую, поэтому в Java и Scala класс Bar компилируется в класс JVM под названием Foo$Bar. Экземпляры внутренних классов почти всегда содержат ссылку на своего владельца - "внешнюю ссылку".

Теперь, когда у вас есть соответствие шаблона по типу, зависящему от пути (например, в коде), компилятор Scala будет генерировать байт-код, который выполняет две функции: он проверит класс (поэтому он будет проверять что объект, который он получил, является экземпляром Foo$Bar), и он проверит внешнюю ссылку (поэтому он проверит, что внешняя ссылка полученного объекта foo).

Однако в вашем коде компилятор не может найти способ проверить внешнюю ссылку, потому что вы объявили свой внутренний класс окончательным.

Итак, если вы проигнорируете предупреждение, ваш шаблон будет соответствовать всем экземплярам Foo$Bar, даже если они не принадлежат foo. У вас будет лучшая идея, чем у меня, будет ли это проблемой.

Или вы можете исправить это, сделав свой внутренний нечетный класс.

Ps, я не совсем уверен, почему компилятор Scala не может проверить внешние ссылки на конечных внутренних классах, но я обнаружил, что если внутренний класс является окончательным, то outer$ является закрытым, тогда как if он не является окончательным, outer$ является общедоступным. Возможно, стоит подумать о том, что это такое.

Update

Оказывается, это известная проблема - SI-4440. Компилятор Scala отбрасывает внешние ссылки для конечных внутренних классов, если они не используются (что является неопределенно законным, так как нет возможности использования их подклассов). Одним из положительных последствий этого является то, что внешние классы могут быть собраны в мусор, пока внутренние классы все еще используются, поэтому разработчики Scala неохотно повторно вводят внешние ссылки, опасаясь ввести утечки памяти.