scala隐士转换理解

2023-11-07 19:20
文章标签 理解 转换 scala 隐士

本文主要是介绍scala隐士转换理解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

Scala提供的隐式转换特性可以在效果上给一个类增加一些方法,或者用于接收不同类型的对象.然而使用Scala的隐式转换是有一定的限制的,总结如下:implicit关键字只能用来修饰方法、变量(参数)和伴随对象。
隐式转换的方法(变量和伴随对象)在当前范围内才有效。如果隐式转换不在当前范围内定义(比如定义在另一个类中或包含在某个对象中),那么必须通过import语句将其导。举例package demo {
package util {import java.util.Date
import java.text.SimpleDateFormatobject DateUtil {class DateWrapper(date: Date) {def format(str: String) = new SimpleDateFormat(str).format(date)}implicit def toDateWrapper(date: Date) = new DateWrapper(date)
}
}package service {
import java.text.SimpleDateFormat
import java.util.Date// 注: 必须将object Rest的定义放在class Rest之前,或者显式指出str2Date的返回类型,否则编译不通过object Rest{class RichDate(str: String){def toDate(): Date = new SimpleDateFormat("yyyy-MM-dd").parse(str)}implicit def str2Date(str: String) = new RichDate(str)}class Rest {import util.DateUtil._import Rest._// 必须把demo.util包下的伴随对象DateWrapper中的所有成员引进来def today = new Date().format("yyyy-MM-dd");// 伴随对象 Rest中定义了一个隐式转换函数def getDate(str: String): Date = str.toDate();}}object SC4 {import demo.service.Restdef main(args: Array[String]) ={println (new Rest().today)println(new Rest().getDate("2011-01-09"))}
}
}
但有一种情况例外,源类型或目标类型(包括源类型和目标类型的类型变量的类型)的伴随对象中隐式转换函数(或变量,伴随对象)不需要显示导入。 比如package demo{
object MyTestApp{def main(args: Array[String]): Unit = {val myTest = new MyTest();myTest.printInt(4)myTest.printYourTest(myTest)  // the source type is of MyTest while the target require YourTestmyTest.fuxkTest()  //  there is no method on MyTest but we can still call it }
}class YourTest{override def toString = "Your Test"def  fuxkTest() = print("fuxk Test");
}object YourTest{implicit def myTest2YourTest = new YourTest}class MyTest{import MyTest._def printStr(str: String) = println(str)// you can't do it like `printStr(i)` unless you bring the implicit converter `MyTest.int2String`into scopedef printInt(i: Int) = printStr(i)def printYourTest(obj: YourTest) = println(obj)def getYorTest(): YourTest = this;
}object  MyTest {implicit def int2String(i: Int ): String = i.toStringimplicit def myTest2YourTest(obj: MyTest): YourTest= new YourTest
//  implicit val myTest2YourTest = (obj : MyTest) => new YourTest
}
}  
这样规定的好处是,通过限制隐式转换的有效范围,使维护和理解代码变得相对容易.
一般来说,scala编译器会首先在方法调用处的当前范围内查找隐式转换函数;如果没有找到,会尝试在源类型或目标类型(包括源类型和目标类型的类型变量的类型)的伴随对象中查找转换函数,如果还是没找到,则拒绝编译。 比如: object ABCDMain extends App {class Bclass C {override def toString() = "I am C";def printC(c: C) = println(c);}class Dimplicit def B2C(b: B) = {println("B2C")new C}implicit def D2C(d: D) = {println("D2C")new C}new D().printC(new B) 
}
运行上述代码,先调用 D2C转换函数将new D()转换成C类, 然后调用C类的printC方法;但发现传入的参数类型是B类,于是搜索当前范围有无合适的转换函数,发现B2C转换函数符合要求。又比如:object ABCDMain extends App {class Aobject A {//  implicit def A2C(a: A) = {//    println("A.A2C");//    new C//  }}class C {override def toString() = "I am C";def printC(c: C) = println(c);}object C {implicit def A2C(a: A) = {println("C.A2C");new C}}class Dimplicit def D2C(d: D) = {println("D2C")new C}new D().printC(new A)
}
运行上述代码,先调用 D2C转换函数将new D()转换成C类, 然后调用C类的printC方法, 但发现传入的参数类型是A类。由于当前范围无合适的转换函数,故搜索object A和object C内有无合适的转换函数,最后发现object A内有合适的转换函数。
如果同时在object A和object C内发现合适的转换函数,有可能导致编译错误。再比如:
object ABCDMain extends App {class Aobject A{//implicit def MA2MC(ma: M[A]) ={//    new M[C];//}}class Cobject C{implicit def MA2MC(ma: AnyRef) = {new M[C];}}class D {def printM(m: M[C]) = println("i am M[C]");} class M[T]object M {implicit def MA2MC(ma: M[A]) = {new M[C];}}new D().printM(new M[A]) 
}
运行上述代码,printM需要传入类型为M[C]的参数,由于传入了类型为M[A],又在当前范围内没有合适转换函数, 因此同时在object M,object A和object C内搜索合适的转换函数,如果发现两个或以上合适的转换函数,那么有可能导致编译错误。源类型和目标类型最多只发生一次隐式转换。举例class A{
override def toString() = "I am A";
}
class B{
override def toString() = "I am B";
}
class C{
override def toString() = "I am C";
}
implicit def A2B(a: A) = new B
implicit def B2C(b: B) = new Cdef printC(c: C) = println(c);
printC(new B); // 会调用B2C函数进行隐式转换
printC(new A); // 对 new A 不能连续做两次隐式转换
执行printC(new A)时会报类型不匹配的错
注意:这里所说的”最多只发生一次隐式转换“的意思是,源类型最多只经过一次函数转换变成目标类型(或与目标类型兼容的类型),但在一次方法调用中,可能发生多次源类型和目标类型之间的转换。
比如class C {override def toString() = "I am C";def printC(c: C) = println(c);
}class D implicit def B2C (b: B) = {println("B2C");new C
}
implicit def D2C(d: D) = {println("D2C");new C
}new D().printC(new B); // 这里会发生两次隐式转换, 一次是D2C, 一次是B2C
另外,当函数定义了隐式参数时,也可能发生多次隐式转换:object Main extends App {class PrintOps() {def print(implicit i: Int) = println(i);}implicit def user2PrintOps(s: String) = {println("use2PrintOps")new PrintOps}implicit def str2int(implicit s: String, implicit l: List[Int]): Int = {println("str2int")Integer.parseInt(s)}implicit def getString = {println("getString")"123"}implicit def newList = {println("newList")List(2)}"a".print
}
运行上述代码,首先调用user2PrintOps函数将"a"转换成PrintOps, 然后调用print方法。由于调用print时没有显式提供implicit参数,因此尝试在当前范围内搜索合适的implicit转换值。编译器发现str2int这个隐式转换函数能提供print所需int类型,但str2int又需要一个隐式的String和一个隐式的List[Int]。编译器继续在当前范围内搜索,最后发现getString和newList这两个隐式转换函数能分别提供一个String实例和一个List[Int]实例。至此编译器知道要先调用getString和newList,再调用str2int,最后调用print。 如果当前的类型匹配或类型兼容,则不会进行隐式转换。举例:class AA extends A{
override def toString() = "I am AA which inherits from A"
}
implicit def AA2A(aa: AA) = {println("AA --> A");new A
}def printA(a: A) = println(a);
printA(new AA)  // 因为AA是A的子类型,所以可以把AA类型的值传递给A类型的变量如果当前范围内有两个或以上合适的隐式转换函数,Scala会怎么处理呢?
在Scala 2.7以及之前的版本中,编译器会发出错误。这跟重载的情况是一样的。比如有两个重载方法foo,一个接收String参数,另一个接收AnyRef参数,当foo(null) 这样写的时候,编译器会拒绝编译。但在Scala 2.8中,编译器会选择foo(String)这个重载方法,即编译器会选择一个更具体的方法。同样当碰到两个或两个以上合适的隐式转换函数时,编译器也会选择一个更具体的方法。至于哪个方法被认为更具体,可以根据以下的规则进行判断:a) 前者的函数参数类型是后者的子类型,则前者更具体 b)  假设两个隐式转换函数都定义在一个类中,如果前者所在的类是后者的子类,那么前者更具体由于隐式转换会给代码带来“魔幻”效果,对于不熟悉这种特性的人会感觉难受。 笔者曾经发现一个利用隐式转换改变方法执行顺序的例子: // TernaryOp对象提供了类似java的三目运算符号操作
object TernaryOp {class Ternary[T](t: T) {println("Ternary") def is[R](bte: BranchThenElse[T,R]) = {println("is ... ")if (bte.branch(t)) bte.then(t) else bte.elze(t)} }class Branch[T](branch: T => Boolean) {println("branch");def ?[R] (then: T => R) = new BranchThen(branch,then)}class BranchThen[T,R](val branch: T => Boolean, val then: T => R){println("BranchThen")}class Elze[T,R](elze: T => R) {println("Elze")def :: (bt: BranchThen[T,R]) = new BranchThenElse(bt.branch,bt.then,elze)}class BranchThenElse[T,R](val branch: T => Boolean, val then: T => R, val elze: T => R)implicit def any2Ternary[T](t: T) = new Ternary(t)implicit def fct2Branch[T](branch: T => Boolean) = new Branch(branch)implicit def fct2Elze[T,R](elze: T => R) = new Elze(elze)def test = {this.getClass.getSimpleName is {s: String => s.endsWith("$")} ? {s: String => s.init} :: {s: String => s}
}
}TernaryOp.test // 思考一下test中会发生怎样转换?若搞不明白,请参考<a href="https://gist.github.com/1388106" target="_blank" rel="nofollow">https://gist.github.com/1388106</a>笔者觉得,如没必要尽量少用隐式转换,毕竟太“魔幻”的代码理解起来还是要费点劲。  BTW:命名函数的参数可以声明为implicit,但implicit必须出现在首位,并且是对所有的参数有效,不能只给某些参数声明为implicit,比如:
def maxFunc(implicit i1: Int, i2: Int) = i1 + i2
maxFunc带有两个implicit参数i1和i2。你无法只声明一个implicit参数。你不能这样写 
def maxFunc( i1: Int, implicit i2: Int) = i1 + i2
也不能这样写: 
def maxFunc(implicit i1: Int, implicit i2: Int) = i1 + i2
如果你只想声明一个implicit,使用curry,如
def maxFunc(implicit i1: Int)(i2: Int) = i1 + i22. 匿名函数不能声明隐式参数,即不能这样写:val f = (implicit s: String) => s+13. 如果一个函数带有implicit参数,则无法通过 _ 得到该函数引用。你尝试这样做是无法编译的:def maxFunc(implicit i1: Int, i2: Int) = i1 + i2
val f = maxFunc _     // 编译错误4. 可以给匿名函数的参数加上implicit,比如:
def h( implicit s: String) = println("here : "+s)
def g(func: String => Int) = {println(func("a"))  
}
g{implicit s =>  h; 2
}
这里的implicit s => h; 2相当于 s => implicit val xx = s; h; 2. 
如果匿名函数有两个参数,貌似不能给参数加上implicit


转载于:https://my.oschina.net/u/1169079/blog/614381

这篇关于scala隐士转换理解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/365734

相关文章

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是

分布式系统的个人理解小结

分布式系统:分的微小服务,以小而独立的业务为单位,形成子系统。 然后分布式系统中需要有统一的调用,形成大的聚合服务。 同时,微服务群,需要有交流(通讯,注册中心,同步,异步),有管理(监控,调度)。 对外服务,需要有控制的对外开发,安全网关。

Java IO 操作——个人理解

之前一直Java的IO操作一知半解。今天看到一个便文章觉得很有道理( 原文章),记录一下。 首先,理解Java的IO操作到底操作的什么内容,过程又是怎么样子。          数据来源的操作: 来源有文件,网络数据。使用File类和Sockets等。这里操作的是数据本身,1,0结构。    File file = new File("path");   字

PDF 软件如何帮助您编辑、转换和保护文件。

如何找到最好的 PDF 编辑器。 无论您是在为您的企业寻找更高效的 PDF 解决方案,还是尝试组织和编辑主文档,PDF 编辑器都可以在一个地方提供您需要的所有工具。市面上有很多 PDF 编辑器 — 在决定哪个最适合您时,请考虑这些因素。 1. 确定您的 PDF 文档软件需求。 不同的 PDF 文档软件程序可以具有不同的功能,因此在决定哪个是最适合您的 PDF 软件之前,请花点时间评估您的

理解java虚拟机内存收集

学习《深入理解Java虚拟机》时个人的理解笔记 1、为什么要去了解垃圾收集和内存回收技术? 当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些“自动化”的技术实施必要的监控和调节。 2、“哲学三问”内存收集 what?when?how? 那些内存需要回收?什么时候回收?如何回收? 这是一个整体的问题,确定了什么状态的内存可以