引出问题
首先来看一个需求:将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)
}
}
好了,到这里我们的问题分析及解决方案就结束了,希望对大家有所帮助。
评论