Scala隐式转换的问题分析--String隐式转换为Int

本文探讨了Scala中将String转换为Int时遇到的隐式转换问题。通过分析源码,揭示了由于Predef中的augmentString方法和自定义的strToInt方法导致的隐式转换冲突。提出了三种解决方案:使用`toInt`替代`toString.toInt`,使用显式调用`toInt`,以及修改自定义隐式方法的返回类型以避免递归调用。

Scala隐式转换的问题分析-String隐式转换为Int

引出问题

首先来看一个需求:将String类型的数字赋值给Int类型的变量。

也就是这样:

val num:Int="20"

要想实现这样的效果,小伙伴们应该都能想到使用隐式方法这个技能。许多小伙伴一鸡冻就撸出了如下的代码:

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)

  /** Creates a string builder buffer as builder for this class */
  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 // 底层代码实现:augmentString(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"  // error
    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)
  }

}

好了,到这里我们的问题分析及解决方案就结束了,希望对大家有所帮助。
by 木子李 更多技术文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值