Option.orNull上的Scala ClassCastException

时间:2019-05-28 11:36:08

标签: scala

当我尝试运行以下代码时:

  def config[T](key: String): Option[T] = {
    //in reality this is a map of various instance types as values
    Some("string".asInstanceOf[T])
  }
  config("path").orNull

我遇到错误:

  

java.lang.String无法强制转换为scala.runtime.Null $   java.lang.ClassCastException

以下尝试正常进行:

config[String]("path").orNull
config("path").getOrElse("")

由于getOrElse令人困惑,因此null为何如此特殊并引发错误。有没有一种方法orNull可以不指定泛型类型?

scalaVersion:=“ 2.12.8”

1 个答案:

答案 0 :(得分:1)

仅说明如何避免使用2 crf.fit(X_train, y_train) ~\Anaconda3\lib\site-packages\sklearn_crfsuite\estimator.py in fit(self, X, y, X_dev, y_dev) 312 313 for xseq, yseq in train_data: --> 314 trainer.append(xseq, yseq) 315 316 if self.verbose: pycrfsuite\_pycrfsuite.pyx in pycrfsuite._pycrfsuite.BaseTrainer.append() pycrfsuite\_pycrfsuite.pyx in pycrfsuite._pycrfsuite.to_item() pycrfsuite\_pycrfsuite.pyx in pycrfsuite._pycrfsuite.to_item() TypeError: object of type 'generator' has no len() 从键入的配置中获取值。

asInstanceOf

然后,您可以像这样使用它。

sealed trait Value extends Product with Serializable
final case class IntValue(value: Int) extends Value
final case class StringValue(value: String) extends Value
final case class BooleanValue(value: Boolean) extends Value

type Config = Map[String, Value]

sealed trait ValueExtractor[T] {
  def extract(config: Config)(fieldName: String): Option[T]
}

object ValueExtractor {
  implicit final val IntExtractor: ValueExtractor[Int] =
    new ValueExtractor[Int] {
      override def extract(config: Config)(fieldName: String): Option[Int] =
        config.get(fieldName).collect {
          case IntValue(value) => value
        }
    }

  implicit final val StringExtractor: ValueExtractor[String] =
    new ValueExtractor[String] {
      override def extract(config: Config)(fieldName: String): Option[String] =
        config.get(fieldName).collect {
          case StringValue(value) => value
        }
    }

  implicit final val BooleanExtractor: ValueExtractor[Boolean] =
    new ValueExtractor[Boolean] {
      override def extract(config: Config)(fieldName: String): Option[Boolean] =
        config.get(fieldName).collect {
          case BooleanValue(value) => value
        }
    }
}

implicit class ConfigOps(val config: Config) extends AnyVal {
  def getAs[T](fieldName: String)(default: => T)
              (implicit extractor: ValueExtractor[T]): T =
    extractor.extract(config)(fieldName).getOrElse(default)
}

现在,问题就变成了如何从原始源创建类型化的配置。
更好的是,如何直接将配置映射到 case类

但是,这些操作更为复杂,最好使用已经完成的操作,例如pureconfig


仅作为一项学术练习,让我们看看我们是否可以支持val config = Map("a" -> IntValue(10), "b" -> StringValue("Hey"), "d" -> BooleanValue(true)) config.getAs[Int](fieldName = "a")(default = 0) // res: Int = 10 config.getAs[Int](fieldName = "b")(default = 0) // res: Int = 0 config.getAs[Boolean](fieldName = "c")(default = false) // res: Boolean = false Lists

让我们从列表开始,一个幼稚的方法是为列表的值提供另一个case类,并为每种类型的列表创建一个提取器工厂(此过程正式称为隐式派生)

Maps

现在,您可以像这样使用它。

import scala.reflect.ClassTag

final case class ListValue[T](value: List[T]) extends Value

...

// Note that, it has to be a def, since it is not only one implicit.
// But, rather a factory of implicits.
// Also note that, it needs another implicit parameter to construct the specific implicit.
// In this case, it needs a ClasTag for the inner type of the list to extract.
implicit final def listExtractor[T: ClassTag]: ValueExtractor[List[T]] =
  new ValueExtractor[List[T]] {
    override def extract(config: Config)(fieldName: String): Option[List[T]] =
      config.get(fieldName).collect {
        case ListValue(value) => value.collect {
          // This works as a safe caster, which will remove all value that couldn't been casted.
          case t: T => t
        }
      }
  }

但是,如果您需要其他通用类型的列表(例如列表列表),则此方法仅限于普通类型。然后,这将无法工作。

val config = Map("l" ->ListValue(List(1, 2, 3)))

config.getAs[List[Int]](fieldName = "l")(default = List.empty)
// res: List[Int] = List(1, 2, 3)
config.getAs[List[String]](fieldName = "l")(default = List("Hey"))
// res: String = List() - The default is not used, since the field is a List...
// whose no element could be casted to String.

这里的问题是类型擦除, ClassTags 无法解决,您可以尝试使用 TypeTags 保留完整的类型,但是解决方案变得更加麻烦。
对于Maps,解决方案非常相似,尤其是如果您将键类型固定为val config = Map("l" ->ListValue(List(List(1, 2), List(3)))) val l = config.getAs[List[List[String]]](fieldName = "l")(default = List.empty) // l: List[List[String]] = List(List(1, 2), List(3)) ???!!! l.head // res: List[String] = List(1, 2) l.head.head // java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String (假设您真正想要的是嵌套配置)。但是,这篇文章现在太长了,因此我将其留给读者练习。


但是,正如已经说过的那样,这很容易被打破,并且并不完全可靠。
有更好的方法,但是我自己对这些(还)并不熟练,即使我愿意,答案也会更长,而且根本没有必要。

幸运的是,即使 pureconfig 不直接支持 YAML ,也有module可以使用,pureconfig-yaml
我建议您看一下该模块,如果还有其他问题,请提出一个新问题,直接标记 pureconfig yaml 。另外,如果只是一个小疑问,您可以尝试在gitter channel中提问。