隐式转换不符合预期

时间:2016-06-10 11:02:31

标签: scala

我是否误解了Scala中的implciits如何工作 - 考虑到Scala中的以下特征,

trait hasConfig {

  implicit def string2array(s: java.lang.String): Array[String] = {
    LoadedProperties.getList(s)
  }

  implicit def string2boolean(s: java.lang.String) :  java.lang.Boolean = {
    s.toLowerCase() match {
      case "true" => true
      case "false" => false
    }
  }

  var config: Properties = new Properties()
  def getConfigs : Properties = config
  def loadConfigs(prop:Properties) : Properties = {
    config = prop
    config
  }

  def getConfigAs[T](key:String):T = {
    if (hasConfig(key)) {
      val value : T = config.getProperty(key).asInstanceOf[T]
      value
    }
    else throw new Exception("Key not found in config")
  }

  def hasConfig(key: String): Boolean = {
    config.containsKey(k)
  }
}

虽然java.util.properties包含(String,String)键值对,但由于定义了隐式转换,我希望以下代码能够正常工作,

class hasConfigTest extends FunSuite {
  val recModel = new Object with hasConfig
  //val prop = LoadedProperties.fromFile("test") Read properties from some file
  recModel.loadConfigs(prop)

  test("test string paramater") {
    assert(recModel.getConfigAs[String]("application.id").equals("framework"))
  }

  test("test boolean paramater") {
    assert(recModel.getConfigAs[Boolean]("framework.booleanvalue") == true) 
    //Property file contains framework.booleanvalue=true
    //expected to return java.lang.boolean, get java.lang.string

  }
}

但是,我收到以下错误,

java.lang.String cannot be cast to java.lang.Boolean
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean

为什么implcit转换没有解决这个问题?

3 个答案:

答案 0 :(得分:3)

它不起作用,因为强制转换(asInstanceOf)与隐式转换完全不同。有多种方法可以解决这个问题。

隐式转换

如果您想使用硬核隐式转换魔术,您应该像这样重写getConfigAs方法:

def getConfig(key:String): String = {
  if (hasConfig(key)) {
    val value: String = config.getProperty(key)
    value
  }
  else throw new Exception("Key not found in config")
}

使用getConfig时,您必须将转化内容导入当前范围。

val recModel = new Object with hasConfig
import recModel._
recModel.loadConfigs(prop)
val value: Boolean = recModel.getConfig("framework.booleanvalue")

隐式参数

更好的方法是保留当前的API,但之后您必须引入隐式参数,因为getConfigAs的实现需要访问转换。

def getConfigAs[T](key:String)(implicit conv: String => T): T = {
  if (hasConfig(key)) {
    val value: String = config.getProperty(key)
    value
  }
  else throw new Exception("Key not found in config")
}

您仍然需要在使用网站上导入必要的转换。

val recModel = new Object with hasConfig
import recModel._
recModel.loadConfigs(prop)
val value = recModel.getConfigAs[Boolean]("framework.booleanvalue")

类型类

避免必须导入转化(并且可能意外隐式转换各种Strings)的方法是引入一种新类型来对转化进行编码。然后,您可以在其伴随对象中实现转换,隐式搜索可以在不导入它们的情况下找到它们。

trait Converter[To]{
  def convert(s: String): To
}
object Converter {
  implicit val string2array: Converter[Array[String]] = new Converter[Array[String]] {
    def convert(s: String): Array[String] = 
      LoadedProperties.getList(s)
  }

  implicit val string2boolean: Converter[Boolean] = new Converter[Boolean] {
    def convert(s: String): Boolean = 
      s.toLowerCase() match {
        case "true" => true
        case "false" => false
      } 
  }
}

然后您可以更改getConfigAs方法。

def getConfigAs[T](key:String)(implicit conv: Converter[T]): T = {
  if (hasConfig(key)) {
    val value: String = config.getProperty(key)
    conv.convert(value)
  }
  else throw new Exception("Key not found in config")
}

并使用它。

val recModel = new Object with hasConfig
recModel.loadConfigs(prop)
val value = recModel.getConfigAs[Boolean]("framework.booleanvalue")

您可能还想查看here

答案 1 :(得分:1)

隐式转换应在范围内定义,例如在封闭对象中或导入到当前范围中。在您的情况下,它们应该在hasConfigTest类的范围内定义。

http://docs.scala-lang.org/tutorials/FAQ/finding-implicits

这是一个简单的可重复示例:

object m {
  implicit def string2boolean(s: String): Boolean = {
    s.toLowerCase() match {
      case "true"  => true
      case "false" => false
    }
  } //> string2boolean: (s: String)Boolean
  println(false || "true") //> true
  println(false || "false") //> false
}

答案 2 :(得分:1)

我认为你想说的是这样的:

import java.util.Properties

object LoadedProperties {
  def getList(s: String): Array[String] = Array.empty
}
object hasConfig {
  sealed trait ConfigReader[T] {
    def read(conf: String): T
  }
  implicit object BooleanConfigReader extends ConfigReader[Boolean] {
    override def read(conf: String): Boolean = conf.toLowerCase() match {
      case "true" => true
      case "false" => false
    }
  }
  implicit object ArrayConfigReader extends ConfigReader[Array[String]] {
    override def read(s: String): Array[String] = {
      LoadedProperties.getList(s)
    }
  }
  var config: Properties = new Properties()
  def getConfigs: Properties = config
  def loadConfigs(prop: Properties): Properties = {
    config = prop
    config
  }
  def getConfigAs[T](key: String)(implicit reader: ConfigReader[T]): T = {
    val prop = config.getProperty(key)
    if  (prop == null)
      throw new Exception("Key not found in config")
    reader.read(prop)
  }
}

val props = new Properties()
props.setProperty("a", "false")
props.setProperty("b", "some")
hasConfig.loadConfigs(props)
hasConfig.getConfigAs[Boolean]("a")
hasConfig.getConfigAs[Array[String]]("a")