Scala隐式转换和隐式参数详解

目录
  • Scala隐式转换和隐式参数
    • 隐式转换
    • 隐式参数
    • 隐式类
    • 隐式转换和隐式参数的导入
  • 总结

Scala隐式转换和隐式参数

隐式转换

隐式转换是指在Scala编译器进行类型匹配时,如果找不到合适的类型,那么隐式转换会让编译器在作用范围内自动推导出来合适的类型。
隐式转换的作用是可以对类的方法进行增强,丰富现有类库的功能,或者让不同类型之间可以相互转换。
隐式转换的定义是使用关键字implicit修饰的函数,函数的参数类型和返回类型决定了转换的方向。

例如,下面定义了一个隐式转换函数,可以把Int类型转换成String类型:

// 定义隐式转换函数
implicit def intToString(x: Int): String = x.toString

这样,在需要String类型的地方,就可以直接传入一个Int类型的值,编译器会自动调用隐式转换函数进行转换:

// 使用隐式转换
val s: String = 123 // 相当于 val s: String = intToString(123)
println(s.length) // 输出 3

注意,隐式转换函数只与函数的参数类型和返回类型有关,与函数名称无关,所以作用域内不能有相同的参数类型和返回类型的不同名称隐式转换函数。

另外,如果在定义隐式转换函数时使用了柯里化函数形式,那么可以实现多个参数的隐式转换:

// 定义柯里化形式的隐式转换函数
implicit def add(x: Int)(y: Int): Int = x + y

这样,在需要两个Int类型参数的地方,就可以直接传入一个Int类型的值,编译器会自动调用隐式转换函数进行转换:

// 使用柯里化形式的隐式转换
val z: Int = 10(20) // 相当于 val z: Int = add(10)(20)
println(z) // 输出 30

隐式参数

隐式参数是指在定义方法时,方法中的部分参数是由implicit修饰的。

隐式参数的作用是可以让调用者省略掉一些不必要或者重复的参数,让代码更简洁和优雅。

隐式参数的定义是在方法签名中使用implicit关键字修饰某个或某些参数。

例如,下面定义了一个方法,它有两个参数,第一个是普通参数,第二个是隐式参数:

// 定义方法,其中一个参数是隐式参数
def sayHello(name: String)(implicit greeting: String): Unit = {
  println(s"$greeting, $name!")
}

这样,在调用这个方法时,就不必手动传入第二个参数,Scala会自动在作用域范围内寻找合适类型的隐式值自动传入。

例如,下面定义了一个字符串类型的隐式值,并调用了上面定义的方法:

// 定义字符串类型的隐式值
implicit val hi: String = "Hi"

// 调用方法,省略第二个参数
sayHello("Alice")
// 相当于 sayHello("Alice")(hi)
println(s"Hi, Alice!")

注意,如果在定义隐式参数时只有一个参数是隐式的,那么可以直接使用implicit关键字修饰参数,而不需要使用柯里化函数形式。

例如,下面定义了一个方法,它只有一个参数,且是隐式的:

// 定义方法,只有一个参数且是隐式的
def sayBye(implicit name: String): Unit = {
  println(s"Bye, $name!")
}

这样,在调用这个方法时,就不需要创建类型不传入参数,Scala会自动在作用域范围内寻找合适类型的隐式值自动传入。

例如,下面定义了一个字符串类型的隐式值,并调用了上面定义的方法:

// 定义字符串类型的隐式值
implicit val bob: String = "Bob"

// 调用方法,不传入参数
sayBye // 相当于 sayBye(bob)
println(s"Bye, Bob!")

隐式类

隐式类是指在定义类时前面加上implicit关键字的类。

隐式类的作用是可以让一个类拥有另一个类的所有方法和属性,或者给一个类添加新的方法和属性。

隐式类的定义是在对象或者包对象中使用implicit关键字修饰类的声明。

例如,下面定义了一个隐式类,可以把String类型转换成拥有reverse方法的类:

// 定义隐式类
object StringUtils {
  implicit class StringImprovement(val s: String) {
    def reverse: String = s.reverse
  }
}

这样,在需要使用reverse方法的地方,就可以直接传入一个String类型的值,编译器会自动调用隐式类的构造器进行转换:

// 使用隐式类
import StringUtils._ // 导入隐式类所在的对象

val s: String = "Hello"
println(s.reverse) // 输出 olleH

注意,隐式类必须有且只有一个参数,并且参数类型不能是目标类型本身。

另外,如果在定义隐式类时使用了泛型参数,那么可以实现多种类型之间的转换:

// 定义泛型参数的隐式类
object MathUtils {
  implicit class NumberImprovement[T](val x: T)(implicit numeric: Numeric[T]) {
    def plusOne: T = numeric.plus(x, numeric.one)
  }
}

这样,在需要使用plusOne方法的地方,就可以直接传入任何数值类型的值,编译器会自动调用隐式类的构造器进行转换:

// 使用泛型参数的隐式类
import MathUtils._ // 导入隐式类所在的对象

val x: Int = 10
println(x.plusOne) // 输出 11

val y: Double = 3.14
println(y.plusOne) // 输出 4.14

隐式转换和隐式参数的导入

Scala提供了两种方式来导入隐式转换和隐式参数:手动导入和自动导入。

手动导入是指在需要使用隐式转换或者隐式参数的地方,使用import语句导入相应的对象或者包对象中定义的隐式内容。

例如,上面使用到的两个例子都是手动导入了StringUtilsMathUtils对象中定义的隐式内容。

手动导入的优点是可以控制导入的范围和精度,避免不必要的冲突和歧义。
手动导入的缺点是需要编写额外的代码,可能会增加代码的长度和复杂度。

自动导入是指在不需要使用import语句的情况下,Scala会自动在一些特定的位置寻找隐式转换或者隐式参数。

例如,Scala会自动导入以下位置定义的隐式内容:

当前作用域内可见的隐式内容与源类型或者目标类型相关联的隐式内容与隐式参数类型相关联的隐式内容

当前作用域内可见的隐式内容是指在当前代码块中定义或者引用的隐式内容。

例如,下面定义了一个隐式转换函数和一个隐式值,在当前作用域内可以直接使用:

// 定义当前作用域内可见的隐式内容
implicit def doubleToInt(x: Double): Int = x.toInt
implicit val pi: Double = 3.14

// 使用当前作用域内可见的隐式内容
val n: Int = pi // 相当于 val n: Int = doubleToInt(pi)
println(n) // 输出 3

与源类型或者目标类型相关联的隐式内容是指在源类型或者目标类型的伴生对象中定义的隐式内容。

例如,下面定义了一个Person类和一个Student类,并在它们的伴生对象中分别定义了一个隐式转换函数,可以把Person转换成Student,或者把Student转换成Person

// 定义Person类和Student类
class Person(val name: String)
class Student(val name: String, val score: Int)

// 定义Person类的伴生对象,其中有一个隐式转换函数,可以把Person转换成Student
object Person {
  implicit def personToStudent(p: Person): Student = new Student(p.name, 0)
}

// 定义Student类的伴生对象,其中有一个隐式转换函数,可以把Student转换成Person
object Student {
  implicit def studentToPerson(s: Student): Person = new Person(s.name)
}

这样,在需要使用Person或者Student类型的地方,就可以直接传入另一种类型的值,编译器会自动调用伴生对象中定义的隐式转换函数进行转换:

// 使用与源类型或者目标类型相关联的隐式内容
def sayName(p: Person): Unit = {
  println(s"Hello, ${p.name}!")
}

def sayScore(s: Student): Unit = {
  println(s"Your score is ${s.score}.")
}

val alice = new Person("Alice")
val bob = new Student("Bob", 100)

sayName(alice) // 输出 Hello, Alice!
sayName(bob) // 相当于 sayName(studentToPerson(bob)),输出 Hello, Bob!

sayScore(alice) // 相当于 sayScore(personToStudent(alice)),输出 Your score is 0.
sayScore(bob) // 输出 Your score is 100.

与隐式参数类型相关联的隐式内容是指在隐式参数类型的伴生对象中定义的隐式内容。

例如,下面定义了一个Ordering[Int]类型的隐式参数,并在它的伴生对象中定义了一个隐式值:

// 定义Ordering[Int]类型的隐式参数
def max(x: Int, y: Int)(implicit ord: Ordering[Int]): Int = {
  if (ord.gt(x, y)) x else y
}

// 定义Ordering[Int]类型的伴生对象,其中有一个隐式值
object Ordering {
  implicit val intOrdering: Ordering[Int] = new Ordering[Int] {
    def compare(x: Int, y: Int): Int = x - y
  }
}

这样,在调用max方法时,就不需要手动传入第二个参数,Scala会自动在Ordering对象中寻找合适类型的隐式值自动传入:

// 使用与隐式参数类型相关联的隐式内容
val a = 10
val b = 20
println(max(a, b)) // 相当于 println(max(a, b)(intOrdering)),输出 20

自动导入的优点是可以省略掉一些不必要或者重复的代码,让代码更简洁和优雅。

自动导入的缺点是可能会导致一些不可预见或者难以发现的错误,或者让代码的逻辑不够清晰和明确。

总结

Scala隐式转换和隐式参数是两个非常强大的功能,它们可以让我们编写更灵活和优雅的代码,但也需要注意一些潜在的问题和风险。

在使用隐式转换和隐式参数时,我们应该遵循以下一些原则:

  • 尽量使用显式的方式来调用或者传递参数,只有在必要或者有明显好处的情况下才使用隐式的方式。
  • 尽量减少隐式转换和隐式参数的数量和范围,避免出现冲突和歧义。
  • 尽量给隐式转换和隐式参数起一个有意义和易于理解的名称,方便阅读和维护代码。
  • 尽量使用编译器提供的提示和警告来检查和调试隐式转换和隐式参数的使用情况。

一般来说,使用隐式转换和隐式参数的时机有以下几种:

  • 当你想要给一个已有的类添加新的方法或者属性,而又不想修改或者继承这个类时,你可以使用隐式类来实现。
  • 当你想要让两个不同类型的对象可以相互转换,或者让一个对象可以调用另一个对象的方法时,你可以使用隐式转换函数来实现。
  • 当你想要省略掉一些不必要或者重复的参数,或者让方法的调用更加灵活和优雅时,你可以使用隐式参数来实现。
  • 当你想要实现一些泛型编程的技巧,比如类型类,上下文界定,隐式证明等时,你可以使用隐式转换和隐式参数来实现。

到此这篇关于Scala隐式转换和隐式参数的文章就介绍到这了,更多相关Scala隐式转换和隐式参数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • scala 隐式转换与隐式参数的使用方法

    隐式转换和隐式参数 Scala总共有三个地方会使用隐式定义: 转换到一个预期的类型 对某个(成员)选择接收端(字段.方法调用等)的转换 隐式参数 隐式规则 标记规则:只有标记为implicit的定义才可用.可标记任何变量.函数.对象 作用域规则:被插入的隐式转换必须是当前作用域的单个标识符,或者跟隐式转换的源类型或目标类型有关联 每次一个规则:每次只能有一个隐式定义被插入 比如编译器绝不会将x+y重写为convert2(convert1(x))+y 显示优先规则:只要代码按编写的样子能通过类型检

  • Scala隐式转换和隐式参数详解

    目录 Scala隐式转换和隐式参数 隐式转换 隐式参数 隐式类 隐式转换和隐式参数的导入 总结 Scala隐式转换和隐式参数 隐式转换 隐式转换是指在Scala编译器进行类型匹配时,如果找不到合适的类型,那么隐式转换会让编译器在作用范围内自动推导出来合适的类型.隐式转换的作用是可以对类的方法进行增强,丰富现有类库的功能,或者让不同类型之间可以相互转换.隐式转换的定义是使用关键字implicit修饰的函数,函数的参数类型和返回类型决定了转换的方向. 例如,下面定义了一个隐式转换函数,可以把Int类

  • MySql比较运算符正则式匹配REGEXP的详细使用详解

    一.初始化数据 DROP TABLE IF EXISTS `test_01`; CREATE TABLE `test_01` ( `id` int(0) NOT NULL, `stu` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '学号', `user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_

  • Java多线程之线程池七个参数详解

    ThreadPoolExecutor是JDK中的线程池实现,这个类实现了一个线程池需要的各个方法,它提供了任务提交.线程管理.监控等方法. 下面是ThreadPoolExecutor类的构造方法源码,其他创建线程池的方法最终都会导向这个构造方法,共有7个参数:corePoolSize.maximumPoolSize.keepAliveTime.unit.workQueue.threadFactory.handler. public ThreadPoolExecutor(int corePoolS

  • Go结合反射将结构体转换成Excel的过程详解

    目录 Excel中的一些概念 使用tealeg操作Excel 安装tealeg 使用tealeg新建一个表格 Go结合反射将结构体转换成Excel 反射获取每个Struct中的Tag 通过反射将结构体的值转换成map[excelTag]strucVal 利用反射将一个Silce,Array或者Struct转换成[]map[excelTag]strucVal 通过tealeg将[]map[excelTag]strucVal转换成Excel 运行测试用例验证 Excel中的一些概念 一个excel文

  • Jquery中$.ajax()方法参数详解

    俗说好记性不如个烂笔头,下面是jquery中的ajax方法参数详解,这里整理了一些供大家参考. 1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为String类型的参数,请求方式(post或get)默认为get.注意其他http请求方法,例如put和delete也可以使用,但仅部分浏览器支持. 3.timeout: 要求为Number类型的参数,设置请求超时时间(毫秒).此设置将覆盖$.ajaxSetup()方法的全局设置. 4.async:

  • 基于SpringMVC接受JSON参数详解及常见错误总结

    最近一段时间不想使用Session了,想感受一下Token这样比较安全,稳健的方式,顺便写一个统一的接口给浏览器还有APP.所以把一个练手项目的前台全部改成Ajax了,跳转再使用SpringMVC控制转发.对于传输JSON数据这边有了更深的一些理解,分享出来,请大家指正. 在SpringMVC中我们可以选择数种接受JSON的方式,在说SpringMVC如何接受JSON之前,我们先聊聊什么是JSON.具体的定义我也不赘述了,在JavaScript中我们经常这样定义JSON 对象 var jsonO

  • scrapy爬虫:scrapy.FormRequest中formdata参数详解

    1. 背景 在网页爬取的时候,有时候会使用scrapy.FormRequest向目标网站提交数据(表单提交).参照scrapy官方文档的标准写法是: # header信息 unicornHeader = { 'Host': 'www.example.com', 'Referer': 'http://www.example.com/', } # 表单需要提交的数据 myFormData = {'name': 'John Doe', 'age': '27'} # 自定义信息,向下层响应(respon

  • mybatis多层嵌套resultMap及返回自定义参数详解

    1.两层嵌套,一个list中加另外一个list data:[ {a:123,b:456,c:[{d:7,e:8}]} ] xml文件定义的sql select * from zhy z LEFT JOIN wl w on z.id = w.zid resultMap可以定义: <resultMap id="zhyResultMap" type="zhy的doman实体" extends="zhy自动生成的BaseResultMap">

  • Python深度学习之Keras模型转换成ONNX模型流程详解

    目录 从Keras转换成PB模型 从PB模型转换成ONNX模型 改变现有的ONNX模型精度 部署ONNX 模型 总结 从Keras转换成PB模型 请注意,如果直接使用Keras2ONNX进行模型转换大概率会出现报错,这里笔者曾经进行过不同的尝试,最后都失败了. 所以笔者的推荐的情况是:首先将Keras模型转换为TensorFlow PB模型. 那么通过tf.keras.models.load_model()这个函数将模型进行加载,前提是你有一个基于h5格式或者hdf5格式的模型文件,最后再通过改

随机推荐