热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

14Scala之隐式转换

1.为什么要隐式转换我们经常引入第三方库,但当我们想要扩展新功能的时候通常是很不方便的,因为我们不能直接修改其代码。scala提供了隐式转换机制和隐式

在这里插入图片描述


1.为什么要隐式转换

我们经常引入第三方库,但当我们想要扩展新功能的时候通常是很不方便的,因为我们不能直接修改其代码。scala提供了隐式转换机制和隐式参数帮我们解决诸如这样的问题。

Scala中的隐式转换是一种非常强大的代码查找机制。当函数、构造器调用缺少参数或者某一实例调用了其他类型的方法导致编译不通过时,编译器会尝试搜索一些特定的区域,尝试使编译通过。


2. 优点

通过隐式转换,程序员可以在编写Scala程序时故意漏掉一些信息,让编译器去尝试在编译期间自动推导出这些信息来,这种特性可以极大的减少代码量,忽略那些冗长,过于细节的代码。


3. 概念

简单说,隐式转换就是:当Scala编译器进行类型匹配时,如果找不到合适的候选,那么隐式转化提供了另外一种途径来告诉编译器如何将当前的类型转换成预期类型。


4。隐式转换的规则:


  1. 如果代码无需隐式转换即可通过编译,则不会引入隐式转换
     比如scala中如果有类似java自动装箱的功能,int类型可以自动转化为integer,那么如果你写一个隐士转换,将int转换成integer,那么这个隐式转换将不会被调用。
  2. 标记规则:只有标记为implicit的变量,函数或对象定义才能被编译器当做隐式操作目标。
  3. 作用域规则:插入的隐式转换必须是单一标示符的形式处于作用域中,或与源/目标类型关联在一起。单一标示符是说当隐式转换作用时应该是这样的形式:file2Array(arg).map(fn)的形式,而不是foo.file2Array(arg).map的形式。假设file2Array函数定义在foo对象中,我们应该通过import foo._或者import foo.file2Array把隐式转换导入。简单来说,隐式代码应该可以被"直接"使用,不能再依赖类路径。假如我们把隐式转换定义在源类型或者目标类型的伴生对象内,则我们可以跳过单一标示符的规则。因为编译器在编译期间会自动搜索源类型和目标类型的伴生对象,以尝试找到合适的隐式转换。
  4. 无歧义规则:存在二义性的隐式转换报错
    不能存在多于一个隐式转换使某段代码编译通过。因为这种情况下会产生迷惑,编译器不能确定到底使用哪个隐式转换。
    比如转化String为int的隐式转换,你定义了两个,A和B,那么就不报错,就像你班级里有两个相同名字的学生。老师点名 鬼知道具体是哪个
  5. 单一调用规则:隐式转换只会匹配一次,即隐式转换至多发生一次
    不会叠加(重复嵌套)使用隐式转换。一次隐式转化调用成功之后,编译器不会再去寻找其他的隐式转换。
  6. 显示操作优先规则:当前代码类型检查没有问题,编译器不会尝试查找隐式转换。

如果没有第5条,我将会有个疑问?假设我们第一次隐式转换把int变成String,隐式转换是

implicit def int2String(x : Int) = x.toString
一个Int=>String的隐式转换,import后,Int会被编译器替换为String

会替换,那么如果没第二条规则,我们就惨了,从引入隐式转换开始,到我们程序结束,都是Int会被编译器替换为String,也就是说没有String类型了,int类型有俩了,一个是"10",一个是10,想想多么可怕。所以发生一次是比较好的。想多次使用的时候我们可以多次引用。


5。隐式解析的搜索范围

这一部分的规则有些复杂,根据《Scala In Depth》所描述的,顶层的搜索逻辑是:


  1. 在当前作用域下查找。这种情形又分两种情况,一个是在当前作用域显示声明的implicit元素,另一个通过import导入的implicit元素。
  2. 如果第一种方式没有找到,则编译器会继续在隐式参数类型的隐式作用域里查找。

真正复杂的地方是什么叫一个类型的隐式作用域?一个类型的隐式作用域指的是“与该类型相关联的类型”的所有的伴生对象。

OK,那什么叫作“与一个类型相关联的类型”? 定义如下:


  1. 假如T是这样定义的:T with A with B with C,那么A, B, C的伴生对象都是T的搜索区域。
  2. 如果T是类型参数,那么参数类型和基础类型都是T的搜索部分。比如对于类型List[Foo],List和Foo都是搜索区域
  3. 如果T是一个单例类型p.T,那么p和T都是搜索区域
  4. 如果T是类型注入p#T,那么p和T都是搜索区域。

隐式转换有新旧两种定义方法,旧的定义方法指是的“implict def”形式,这是Scala 2.10版本之前的写法,在Scala 2.10版本之后,Scala推出了“隐式类”用来替换旧的隐式转换语法,因为“隐式类”是一种更加安全的方式,对被转换的类型来说,它的作用域更加清晰可控。

接下来我们通过实例来了解这两种隐式转换的写法。前文提到,隐式转换最为基本的使用场景是:将某一类型转换成预期类型,所以我们下面的例子就以最这种最简单的场景来演示,它们都实现了:将一个String类型的变量隐式转换为Int类型:

简单查找 看列子

class Implicits {implicit val content = "Java Hadoop"
}
object Implicits {implicit val content = "Scala Spark"
}
object ImplicitsAdvanced {def main(args: Array[String]): Unit = {def printContent(implicit content: String) = println(content) implicit val content = "I love spark"import Implicits._ printContent
}

printContent 的隐式参数是String类型,所以会找String类型的隐式参数,
上面例子上有三个可能,“Java Hadoop”, “Scala Spark”, “I love spark”
结果是

I love spark

即它会先在main这个大括号里找满足要求的String类型隐式参数,找到就用,优先级最高。

只有找不到了才会去导入的类里找,把implicit val content = "I love spark"注释掉后
结果

Scala Spark

Scala Spark 它要找的是伴生对象里的隐式参数,而非类里面的隐式参数!

如果再加上一个隐式参数如下

object ImplicitsMsg {implicit val content = "Kafak Zookeeper"
}

导入时这样导入

import ImplicitsMsg.content

结果是

Kafak Zookeeper

说明都是导入的情况下,优先选择具体的,通配符导入的优化级较低。


6。隐式转换的时机


  1. 当方法中的参数的类型与目标类型不一致时
  2. 当对象调用类中不存在的方法或成员时,编译器会自动将对象进行隐式转换

7.隐式转换有四种常见的使用场景:


  1. 将某一类型转换成预期类型
  2. 类型增强与扩展
  3. 模拟新的语法
  4. 类型类

使用方式:
1.将方法或变量标记为implicit
2.将方法的参数列表标记为implicit
3.将类标记为implicit

Scala支持两种形式的隐式转换:
隐式值:用于给方法提供参数
隐式视图:用于类型间转换或使针对某类型的方法能调用成功


8。先看一个例子 “implict def”形式的隐式转换

package source.mscalaobject ImplicitDefDemo {object MyImplicitTypeConversion {implicit def strToInt(str: String) = str.toInt}def main(args: Array[String]) {//compile error!val max = math.max("1", 2);/*import MyImplicitTypeConversion.strToIntval max = math.max("1", 2);*/println(max)}
}

运行直接报错

Error:(11, 20) overloaded method value max with alternatives:(x: Double,y: Double)Double (x: Float,y: Float)Float (x: Long,y: Long)Long (x: Int,y: Int)Intcannot be applied to (String, Int)val max = math.max("1", 2);

然后修改

package source.mscalaobject ImplicitDefDemo {object MyImplicitTypeConversion {implicit def strToInt(str: String) = str.toInt}def main(args: Array[String]) {//compile error!// val max = math.max("1", 2);import MyImplicitTypeConversion.strToIntval max = math.max("1", 2);println(max)}
}

运行结果正确

2
Process finished with exit code 0

这里有疑问?为什么加入import MyImplicitTypeConversion.strToInt这一句代码就可以了呢?


9。隐式值(隐式参数)

假设不引入隐式值试试,( 其中试试了print(person()) 这样编译都会报错)

package source.mscalaobject ImplicitDefDemo {def person(implicit name : String) = namedef main(args: Array[String]) {print(person)}
}

直接报错

Error:(6, 11) could not find implicit value for parameter name: Stringprint(person)
Error:(6, 11) not enough arguments for method person: (implicit name: String)String.
Unspecified value parameter name.print(person)

然后修改,引入隐式值

package source.mscalaobject ImplicitDefDemo {def person(implicit name : String) = namedef main(args: Array[String]) {implicit val p = "mobin" //p被称为隐式值print(person)}
}

运行结果正确

mobin
Process finished with exit code 0

假设我如果传入了值呢?这时候使用的是哪种在呢?

package source.mscalaobject ImplicitDefDemo {def person(implicit name : String) = namedef main(args: Array[String]) {implicit val p = "mobin" //p被称为隐式值print(person("aa"))}
}

运行结果是我们传入的值,隐式转换没起作用,这里符合了 隐式转换的规则中的第一条和第六条

aa
Process finished with exit code 0

如果我们定义了两个隐式值呢?

package source.mscalaobject ImplicitDefDemo {def person(implicit name : String) = namedef main(args: Array[String]) {implicit val p = "mobin" //p被称为隐式值implicit val myp2 = "i am two" //myp2被称为隐式值print(person)}
}

会报错

Error:(9, 11) ambiguous implicit values:both value myp2 of type Stringand value p of type Stringmatch expected type Stringprint(person)Error:(9, 11) could not find implicit value for parameter name: Stringprint(person)Error:(9, 11) not enough arguments for method person: (implicit name: String)String.
Unspecified value parameter name.print(person)

这里的意思是,print(person)因为你没传入参数,而person(implicit name : String) 需要的隐式参数是String类型的,所以去找隐式值,然后根据搜索范围,根据第一条 在当前作用域下查找。这种情形又分两种情况,一个是在当前作用域显示声明的implicit元素,另一个通过import导入的implicit元素。这里有两个String类型的,因此两个都符合,但是编译器不知道调用哪个啊,所以报错。

假设是不同的类型呢?

package source.mscalaobject ImplicitDefDemo {def person(implicit name : String) = namedef main(args: Array[String]) {implicit val p:String = "mobin" //p被称为隐式值 String类型implicit val myp2:Int = 3 //myp2被称为隐式值 int类型print(person)}
}

运行结果正确,虽然在一个搜索范围里有两个隐式值,但是类型不符合

mobin
Process finished with exit code 0

10。隐式视图,(隐式函数)

隐式转换为目标类型:把一种类型自动转换到另一种类型

package source.mscalaobject ImplicitDefDemo {def foo(msg : String) = println(msg)implicit def intToString(x : Int) = x.toStringdef main(args: Array[String]) {foo(10)}
}

如果没有 implicit def intToString(x : Int) = x.toString这个函数,是无法编译的,直接就是错误


11。隐式转换调用类中本不存在的方法

package source.mscalaclass SwingType{def wantLearned(sw : String) = println("兔子已经学会了"+sw)
}
object swimming{implicit def learningType(s : AminalType) = {println("i am run ")new SwingType}
}
class AminalType
object AminalType {def main(args: Array[String]) {import source.mscala.swimming._val rabbit = new AminalTyperabbit.wantLearned("breaststroke") //蛙泳}}

如果没有import source.mscala.swimming._,那么无法编译
编译器在rabbit对象调用时发现对象上并没有wantLearning方法,此时编译器就会在作用域范围内查找能使其编译通过的隐式视图,找到learningType方法后,编译器通过隐式转换将对象转换成具有这个方法的对象,之后调用wantLearning方法
运行结果

i am run
兔子已经学会了breaststroke

可以将隐式转换函数定义在伴生对象中,在使用时导入隐式视图到作用域中即可(如例4的learningType函数)

package source.mscalaclass SwingType{def wantLearned(sw : String) = println("兔子已经学会了"+sw)
}package swimmingPage{object swimming{implicit def learningType(s : AminalType) = {println("i am run ")new SwingType}}
}class AminalType
object AminalType {def main(args: Array[String]) {import source.mscala.swimmingPage.swimming._val rabbit = new AminalTyperabbit.wantLearned("breaststroke") //蛙泳}}

运行结果和上面相同,但是有什么区别呢?没感觉多大区别

像intToString,learningType这类的方法就是隐式视图,通常为Int => String的视图,定义的格式如下:
implicit def originalToTarget ( : OriginalType) : TargetType
其通常用在于以两种场合中:
1.如果表达式不符合编译器要求的类型,编译器就会在作用域范围内查找能够使之符合要求的隐式视图。如例2,当要传一个整数类型给要求是字符串类型参数的方法时,在作用域里就必须存在Int => String的隐式视图

2.给定一个选择e.t,如果e的类型里并没有成员t,则编译器会查找能应用到e类型并且返回类型包含成员t的隐式视图。如例3


12。隐式类

在scala2.10后提供了隐式类,可以使用implicit声明类,但是需要注意以下几点:


  1. 其所带的构造参数有且只能有一个
  2. 隐式类必须被定义在类,伴生对象和包对象里
  3. 隐式类不能是case class(case class在定义会自动生成伴生对象与2矛盾)
  4. 作用域内不能有与之相同名称的标示符

package source.mscalaobject Stringutils {implicit class StringImprovement(val s : String){ //隐式类def increment = s.map(x => (x +1).toChar)}
}
object Main extends App{import source.mscala.Stringutils._println("mobin".increment)
}

编译器在mobin对象调用increment时发现对象上并没有increment方法,此时编译器就会在作用域范围内搜索隐式实体,发现有符合的隐式类可以用来转换成带有increment方法的StringImprovement类,最终调用increment方法。运行结果如下

npcjo
Process finished with exit code 0

推荐阅读
  • 7.4 基本输入源
    一、文件流1.在spark-shell中创建文件流进入spark-shell创建文件流。另外打开一个终端窗口,启动进入spark-shell上面在spark-shell中执行的程序 ... [详细]
  • 2018年人工智能大数据的爆发,学Java还是Python?
    本文介绍了2018年人工智能大数据的爆发以及学习Java和Python的相关知识。在人工智能和大数据时代,Java和Python这两门编程语言都很优秀且火爆。选择学习哪门语言要根据个人兴趣爱好来决定。Python是一门拥有简洁语法的高级编程语言,容易上手。其特色之一是强制使用空白符作为语句缩进,使得新手可以快速上手。目前,Python在人工智能领域有着广泛的应用。如果对Java、Python或大数据感兴趣,欢迎加入qq群458345782。 ... [详细]
  • 本文介绍了在Win10上安装WinPythonHadoop的详细步骤,包括安装Python环境、安装JDK8、安装pyspark、安装Hadoop和Spark、设置环境变量、下载winutils.exe等。同时提醒注意Hadoop版本与pyspark版本的一致性,并建议重启电脑以确保安装成功。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • 本文介绍了Java集合库的使用方法,包括如何方便地重复使用集合以及下溯造型的应用。通过使用集合库,可以方便地取用各种集合,并将其插入到自己的程序中。为了使集合能够重复使用,Java提供了一种通用类型,即Object类型。通过添加指向集合的对象句柄,可以实现对集合的重复使用。然而,由于集合只能容纳Object类型,当向集合中添加对象句柄时,会丢失其身份或标识信息。为了恢复其本来面貌,可以使用下溯造型。本文还介绍了Java 1.2集合库的特点和优势。 ... [详细]
  • MR程序的几种提交运行模式本地模型运行1在windows的eclipse里面直接运行main方法,就会将job提交给本地执行器localjobrunner执行-- ... [详细]
  • Kylin 单节点安装
    软件环境Hadoop:2.7,3.1(sincev2.5)Hive:0.13-1.2.1HBase:1.1,2.0(sincev2.5)Spark(optional)2.3.0K ... [详细]
  • 我们在之前的文章中已经初步介绍了Cloudera。hadoop基础----hadoop实战(零)-----hadoop的平台版本选择从版本选择这篇文章中我们了解到除了hadoop官方版本外很多 ... [详细]
  • Exceptioninthreadmainorg.apache.hadoop.security.AccessControlException:Permissiondenied: ... [详细]
  • 基于,docker,快速,部署,多,需求,spark ... [详细]
  • 原创 | 大数据入门基础系列之ClouderaManager版本的Hive安装部署
    添加服务,一 ... [详细]
  • bat大牛带你深度剖析android 十大开源框架_请收好!5大领域,21个必知的机器学习开源工具...
    全文共3744字,预计学习时长7分钟本文将介绍21个你可能没使用过的机器学习开源工具。每个开源工具都为数据科学家处理数据库提供了不同角度。本文将重点介绍五种机器学习的 ... [详细]
  • Zookeeper 总结与面试题汇总
    Zookeeper总结与面试题汇总,Go语言社区,Golang程序员人脉社 ... [详细]
  •     这里使用自己编译的hadoop-2.7.0版本部署在windows上,记得几年前,部署hadoop需要借助于cygwin,还需要开启ssh服务,最近发现,原来不需要借助cy ... [详细]
author-avatar
feixiang1563122
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有