隐式转换

        隐式转换在scala中具有强大的魔力,能够让本就精简的代码变得更加简洁。但是强大同样意味这难以掌握,刚好我在学习scala的过程中接触到了这种神奇的语言特性,于是不自量力的说说自己的理解。

        无论任何语言特性,复杂的或者简单的,它出现的目的必定是为了解决某些其他语言难以解决或者解决起来十分麻烦的问题。所以为了了解隐式转换,同样我们需要知道它可以解决哪些问题。从一个简单的例子出发。比如我们现在有一个自定义的数字类。然后我们在其中定义了加法。代码类似下图:


隐式转换

可以看到自定义的数字类中有两个重载的方法用来计算加法。看起来似乎是没有问题,但是如果上图中val c = a + b  变成 val c = b + a就会编译失败。因为在Int类中并不存在重载后参数为自定义数字类的加法。所以这个时候我们就需要将a转换类型为自定义数字类这样就能用自定义数字类中的方法来完成计算。当然如果手动来完成这样的操作,看起来实在太繁琐,不符合简洁的风格。同时也会将自己的实现暴露给调用者。于是此时我们就可以用隐式转换来完成这个操作。

隐式转换

如图中所示,不仅可以将Int类型隐式转换为Demo3类型,同时也可以将Double类型,自定义的Demo4类型隐式转换成Demo3类型然后完成计算。

        当然除了隐式方法以外,还有隐式类和隐式参数。比如下面这段代码

隐式转换

首先创建了一个名为MyParis的类,构造器具有长宽高三个参数。然后在其对应的伴生对象中添加了两个implicit class。他们的作用是分别将Int类型和String类型build一个对应类型的高,再添加一个隐式参数,就可以创建一个MyPairs对象。同时由于两个implicit class中的build方法都具有各自类型的隐式参数。于是在下面的作用域中分别定义了两个implicit variable 作为接下来调用build方法时的隐式参数。同时由于scala中 a op b 等同于 a.op(b) 。所以如main方法前两行所写,就可以直接创建对应的MyPairs对象。

        相信到这里关于隐式转换的用法,已经有所了解。但是具体该怎么使用,它的规则是什么呢?隐式转换主要有以下几条规则:

                1.隐式转换仅限于指定的作用域中。

                  也就是说如果需要使用隐式转换,要么直接定义在需要使用的地方。要么定义在伴生对象中,然后import到指定作用域。

                2.同一个对象只能够被隐式转换一次。

                   也就算说即使定义了多个类型衔接的隐式转换,但是只会进行第一次转换。以我粗浅的理解,这是为了避免编译器的复杂度过高,同时避免无意中定义了环形的隐式转换从而导致编译器bug。

                3.当同一个对象可以同时被隐式转换成多种类型时会报错而非选择一个最接近的对象类型。

                    举例来说如果同一个如果同时定义了两个将Int转换为其他类型的隐式方法。那么在调用时编译器无法抉择应该选择哪种从而导致编译错误。

                4.隐式转换不会转换任何已经显示标注类型且通过类型检查的对象

                利用这个规则,我们可以通过显示指定类型来debug隐式转换可能出现的问题。


                说完规则,让我们看看刚才最后一张截图中未谈及的一段代码,求一个序列中的最大数

隐式转换

图中令人最为费解的应该是 implicitly[Ordering[T]]这里。让我们先忽视这个地方。假如使用刚才谈到的隐式转换来完成这个方法,那么我们应该在方法签名中添加隐式参数有关于T的比较类型。大概应该是这个样子

隐式转换

这里添加了隐式参数然后利用隐式参数的比较性质来完成获取最大值。比较上下图中的代码,可以看出,implicitly代替了隐式参数,同时对类型T做了限定。所以比较来看,implicitly的用法渐渐明了,它可以用来获取指定对象类型的隐式转换。你可以在任何地方使用implicitly[Foo]来获取Foo类型的隐式转换。这样就不再需要在方法签名中声明隐式参数啦。

                隐式转换是一种强大的特性,但是如开头所讲,强大意味着难以掌握。除非没有其他手段能够模拟完成目的或者这样的模拟手段造成了代码的大量冗余,否则不要轻易使用隐式转换。