引出问题
首先来看一个需求:将String类型的数字赋值给Int类型的变量。
也就是这样:
要想实现这样的效果,小伙伴们应该都能想到使用隐式方法这个技能。许多小伙伴一鸡冻就撸出了如下的代码:
implicit def strToInt(str:String):Int= {
      str.toInt
 }
*友情提示:*隐式转换的代码要定义在object中哦~~~
定义了如上的隐式方法后,接下来我们来使用一下该隐式方法。接下来来一段完整代码尝尝:
object TestDemo {
  def main(args: Array[String]): Unit = {
    val num:Int = "20"
    println(num)
  }
  
    * 定义的隐式方法
    *    该方法的功能是将String转成Int
    * @param str  需要转换的字符串
    * @return 返回 Int
    */
  implicit def strToInt(str:String):Int= {
    str.toInt
  }
}
代码撸完,感觉还不错,接着我们运行以上代码:哐当,出错啦。。。。
控制台输出以下错误信息:
Error:(17, 5) type mismatch;
 found   : str.type (with underlying type String)
 required: ?{def toInt: ?}
Note that implicit conversions are not applicable because they are ambiguous:
 both method augmentString in object Predef of type (x: String)scala.collection.immutable.StringOps
 and method strToInt in object TestDemo of type (str: String)Int
 are possible conversion functions from str.type to ?{def toInt: ?}
    str.toInt
Error:(17, 9) value toInt is not a member of String
    str.toInt
大伙看错误信息中有个关键的单词ambiguous(模糊不清的,模棱两可的),以上错误信息的大致意思是说:
隐式转换在这里不适用了,因为隐式转换出现了模糊不清的情况,这里有两个方法
一个在object Predef中有一个augmentString方法将x:String转成scala.collection.immutable.StringOps,
另一个在object TestDemo中有strToInt方法将str: String转成了Int
以上两个方法都可以将str: String转成了Int,所以隐式转换出现了模糊不清的情况
看完后估计有的小伙伴还是一头雾水,这到底是说的啥,这到底是为什么??好,那接下来我们就看看其中的究竟。
分析原因
分析String相关的源码
首先我们来看一段代码:
 def main(args: Array[String]): Unit = {
    val str:String = "20"
    val i = str.toInt
    println(i)
  }
以上代码的正确编译运行,输出结果是Int类型的20。
接下来我们按住键盘上的Ctrl,鼠标点击String查看String类型,发现在object Predef中有这么一段代码:
type String  = java.lang.String
通过这段代码我们知道,Scala中的String其实是使用了java中的String。好了,那我们回顾以下java中的String有toInt()方法吗,熟悉Java的肯定立马就能回答:没有。对的,就是没有!!
String中没有toInt()方法,但是这里的str却可以使用toInt(),那说明str.toInt这里发生了隐式转换。没错,这里的str由String转换成了StringOps。这么转换的呢,我们上源码:
implicit def augmentString(x: String): StringOps = new StringOps(x)
在object Perdef中有一个隐式方法augmentString将String转换成了StringOps。接着我们再来看看StringOps的源码:
final class StringOps(override val repr: String) extends AnyVal with StringLike[String] {
  override protected[this] def thisCollection: WrappedString = 
    new WrappedString(repr)
    
  override protected[this] def toCollection(repr: String): WrappedString = 
  new WrappedString(repr)
  
  override protected[this] def newBuilder = StringBuilder.newBuilder
  override def apply(index: Int): Char = repr charAt index
  override def slice(from: Int, until: Int): String = {
    val start = if (from < 0) 0 else from
    if (until <= start || start >= repr.length)
      return ""
    val end = if (until > length) length else until
    repr.substring(start, end)
  }
  override def toString = repr
  override def length = repr.length
  def seq = new WrappedString(repr)
}
在StringOps中没有直接发现toInt()方法,不要慌,仔细看看StringOps继承了StringLike,接着我们看看StringLike源码:
trait StringLike[+Repr] extends Any with scala.collection.IndexedSeqOptimized[Char, Repr] with Ordered[String] {
 
  def toInt: Int         = java.lang.Integer.parseInt(toString)
  
   * @throws java.lang.NumberFormatException  - If the string does not contain a 			parsable long.
   */
  def toLong: Long       = java.lang.Long.parseLong(toString)
}
终于在StringLike中找到了toInt,因为StringOps继承了StringLike,所以StringOps也就有了toInt()。
再看刚才的隐式方法:
implicit def augmentString(x: String): StringOps = new StringOps(x)
隐式方法augmentString将String转换成了StringOps,所以:
 def main(args: Array[String]): Unit = {
    val str:String = "20"
    val i = str.toInt 
    println(i)
  }
好了,通过以上代码分析,我们知道StringOps中存在toInt方法,所以通过隐式转换将str转成StringOps后就可以调用toInt了。
分析Int相关的源码
接下来,我们继续分析Int类的源码:
final abstract class Int private extends AnyVal {
  
  def toInt: Int
  def toLong: Long
  
 }
通过查看Int的源码发现在Int中也存在一个toInt的方法,那么现在如果也存在一个将String转成Int的隐式方法,那么,String也能调用toInt了。分析到这里,我们渐渐的感觉到发现冲突产生的地方了,好的,我们马上来看看我们写的隐式方法的代码:
 
    * 定义的隐式方法
    *    该方法的功能是将String转成Int
    * @param str  需要转换的字符串
    * @return 返回 Int
    */
implicit def strToInt(str:String):Int= {
    
      * 通过分析我们知道StringOps和Int都有toInt方法,所以
      * str.toInt在这里有两个隐式函数都可以进行转换
      * 1、使用scala.Predef 中的
      *       implicit def augmentString(x: String): StringOps = new StringOps(x)
      *   所以str.toInt 就等价于  augmentString(str).toInt
      *
      * 2、使用自己定义这个隐式方法(递归调用)
      *      augmentString 就等价于 strToInt(str).toInt
      *
      *  好了,我们知道隐式转换只能匹配一个,不能有多个,
      *  而这里str.toInt找到两个隐式转换都可以实现,所以出现了之前“模棱两可”的错误信息
      */
    str.toInt
  }
既然分析清楚了出现错误的原因,接下来我们就根据原因来看看如何解决吧。
解决方法的本质就是不要让toInt隐式转换的时候找到多个隐式方法出现“模棱两可”。
解决方案
方案一
使用Integer.parseInt(str)替换str.toInt。完整代码代码如下:
object TestDemo {
  def main(args: Array[String]): Unit = {
    val str:Int = "20"
    println(str)
  }
  implicit def strToInt(str:String):Int= {
    Integer.parseInt(str)
  }
}
使用Integer.parseInt(str)替换str.toInt,没有使用str.toInt了,隐式转换自然就就没有了。
方案二
使用 new StringOps(str).toInt替换str.toInt。完整代码代码如下:
object TestDemo {
  def main(args: Array[String]): Unit = {
    val str:Int = "20"
    println(str)
  }
  implicit def strToInt(str:String):Int= {
    new StringOps(str).toInt
  }
}
使用 new StringOps(str).toInt替换str.toInt。这里使用 new StringOps(str)显式调用了toInt,所以也没有隐式转换了。
方案三
将自己定义的隐式方法implicit def strToInt(str:String):Int的返回值省略,该方法就不能被递归调用了,那这时候str.toInt就只有implicit def augmentString(x: String): StringOps这一个隐式转换方法了,也解决了两个隐式方法的冲突问题。完整代码如下:
object TestDemo {
  def main(args: Array[String]): Unit = {
    val str:Int = "20"  
    println(str)
  }
  implicit def strToInt(str:String) = {
    str.toInt
  }
}
以上代码第四行val str:Int = "20"编译错误,错误信息如下:
Error:(8, 19) type mismatch;
 found   : String("20")
 required: Int
 Note: implicit method strToInt is not applicable here because it comes after the application point and it lacks an explicit result type
    val str:Int = "20"
出现该错误的原因是:
隐式方法没有显式给出返回类型,必须位于应用点之前
所以正确代码如下:
object TestDemo {
  
  implicit def strToInt(str:String) = {
    str.toInt
  }
  
  def main(args: Array[String]): Unit = {
    val str:Int = "20"
    println(str)
  }
}
好了,到这里我们的问题分析及解决方案就结束了,希望对大家有所帮助。
评论