Подтвердить что ты не робот

Scala: кто может это объяснить?

Рассмотрим следующий код Scala:

case class Data[T](value: Option[T]) {
  def get: T = try {
    doGet
  } catch {
    case e: Exception => throw new IllegalArgumentException
  }

  def doGet: T = value match {
    case Some(v) => v
    case None => ().asInstanceOf[T]
  }
}

Data[Unit](None).get
Data[Integer](None).get // which exception is thrown here?

[spoiler] Это ClassCastException; кто может объяснить, почему он не пойман и не заменен на IllegalArgumentException?

PS: Чтобы упредить любые вопросы о том, почему я хотел бы это сделать: это упрощенная версия некоторого кода, который использует json4s для синтаксического анализа некоторой строки в Option[T]; если не удалось выполнить синтаксический анализ None, который является ОК, если T был Unit и не ОК, если T - это другой тип.

4b9b3361

Ответ 1

Описание

Исключение не выбрасывается здесь:

().asInstanceOf[T]

потому что это непроверенный листинг. JVM не может проверить, возможно ли сделать () в T, потому что у него нет информации о T из-за стирания типа.

Вместо этого здесь вызывается исключение

Data[Integer](None).get

потому что результат get передается в Integer, и это то, что JVM может проверить. Итак, ClassCastException на самом деле выбрано вне get.

BTW, javac всегда предупреждает о непроверенных приведениях, я не знаю, почему scalac не делает.

Обход

В какой-то мере можно использовать стирание стилей здесь, используя ClassTag и кастинг на основе отражения:

import scala.reflect.{ClassTag, classTag}

case class Data[T: ClassTag](value: Option[T]) {
  def get: T = try {
    doGet
  } catch {
    case e: Exception => throw new IllegalArgumentException
  }

  def doGet: T = value match {
    case Some(v) => v
    case None => classTag[T].runtimeClass.asInstanceOf[Class[T]].cast(())
  }
}

Hackaround

В этом случае вы можете напрямую проверить ClassTag:

scala> case class Data[T](value: Option[T])(implicit t: ClassTag[T]) {
     | def get: T = value getOrElse (t match {
     |   case ClassTag.Unit => ().asInstanceOf[T]
     |   case _ => throw new IllegalArgumentException
     | })
     | }
defined class Data

scala> Data[Unit](None)
res6: Data[Unit] = Data(None)

scala> .get

scala> Data[Int](None).get
java.lang.IllegalArgumentException