Scala入门系列(十二):隐式转换

引言

Scala提供的隐式转换和隐式参数功能,是非常有特色的功能。是Java等编程语言所没有的功能。它可以允许你手动指定,将某种类型的对象转换成其他类型的对象。通过这些功能可以实现非常强大而且特殊的功能。

Scala的隐式转换,其实最核心的就是定义隐式转换函数,即implicit conversion function。定义的隐式转换函数,只要在编写的程序内引入,就会被Scala自动使用。在程序中使用到隐式转换函数参数类型定义的对象时,会自动将其传入隐式转换函数,转换为另外一种类型的对象并返回。这就是“隐式转换”。

隐式转换函数叫什么名字是无所谓的,因为通常不会由用户手动调用,而是由Scala进行调用。但是如果要使用隐式转换,则需要对隐式转换函数进行导入。因此通常建议将隐式转换函数的名称命名为“one2one”的形式。

隐式转换

要实现隐式转换,只要程序可见的范围内定义隐式转换函数即可。Scala会自动调用隐式转换函数。

隐式转换函数与普通函数唯一的区别就是要以implicit开头,而且最好要定义函数返回类型。

案例:特殊售票窗口(只接受特殊人群:比如学生、老人等)

 
class SpecialPerson(val name: String) 
 
class Student(val name: String) 
 
class Older(val name: String) 
 
// 我们想通过隐式转化把学生和老人转化为特殊人群,这样他们就可以在特殊窗口买票。 
implicit def object2SpecialPerson(obj: Object): SpecialPerson = { 
  if(obj.getClass == classOf[Student]){ val stu = obj.asInstanceOf[Student]; new SpecialPerson(stu.name) } 
  else if (obj.getClass == classOf[Older]) { val older = obj.asInstanceOf[Older]; new SpecialPerson( 
older.name) } 
  else Nil 
} 
 
var ticketNumber = 0 
// 只接受特殊人群的买票服务 
def buySpecialTicket(p: SpecialPerson) = { 
  ticketNumber += 1 
  "T-" + ticketNumber 
} 
 
// 测试 
scala> val stu = new Student("sparks") 
stu: Student = Student@388623ad 
 
scala> val old = new Older("leo") 
old: Older = Older@3453acd2 
// 学生和老人通过隐式转换后均可买特殊票 
scala> buySpecialTicket(stu) 
res9: String = T-1 
 
scala> buySpecialTicket(old) 
res10: String = T-2 

使用隐式转换加强现有类型

隐式转换非常强大的一个功能,就是可以在不知不觉中加强现有类型的功能。也就是说,可以为某个类定义一个加强版的类,并定义互相之间的隐式转换函数,从而让源类在使用加强版类的方法时,由Scala自动进行隐式转换,然后再调用加强类中特有的方法。 
  
案例:超人变身

class Man(val name: String) 
// 定义超人类,具有发射激光方法 
class Superman(val name: String){ 
  def emitLaser = println("emit a laster!") 
} 
// 定义隐式转换函数,将普通人转为超人 
implicit def man2superman(man: Man): Superman = new Superman(man.name) 
 
defined class Man 
defined class Superman 
man2superman: (man: Man)Superman 
 
// 创建普通人类对象sparks 
scala> val sparks = new Man("leo") 
sparks: Man = Man@75c56eb9 
 
// sparks通过隐式转换变身超人,拥有发射激光方法 
scala> sparks.emitLaser 
emit a laster! 

隐式转化函数的作用域与导入

Scala默认会使用两种隐式转换,一种是源类型或目标类型的伴生对象内的的隐式转化函数;一种是当前程序作用域内的可以用唯一标识符表示的隐式转换函数。(上面两个例子都属于第二种情况)

如果隐式转换函数不在上述两种情况下的话,那么就必须手动使用import语法导入,例如:import test._ 。通常建议,仅仅在需要进行隐式转换的地方,比如某个函数或者方法内,用import导入隐式转换函数,这样可以缩小隐式转换函数的作用域,避免不需要的隐式转换。

隐式转换触发条件

  1. 调用某个函数,但是给函数传入的参数类型与函数定义的接收类型不匹配。(案例:特殊售票窗口) 
  2. 使用某个类型的对象,调用某个方法,而这个方法并不存在于该类型时。(案例:超人变身) 
  3. 使用某个类型的对象,调用该类中的某个方法,虽然该类型有这个方法,但是给方法传入的参数类型与定义接收类型不匹配(案例:特殊售票窗口加强版)

  
案例:特殊售票窗口加强版

// 定义售票窗口类 
class TicketHouse { 
  var ticketNumber = 0 
  def buySpecialTicket(p: SpecialPerson) = { 
    ticketNumber += 1 
    "T-" + ticketNumber 
  } 
} 
 
defined class TicketHouse 
// 测试 
scala> val ticketHouse = new TicketHouse 
ticketHouse: TicketHouse = TicketHouse@522b2631 
 
scala> val spark = new Student("sparks") 
spark: Student = Student@588ffeb 
// 当传入一个学生对象时,触发第三种隐式转换条件 
scala> ticketHouse.buySpecialTicket(spark) 
res2: String = T-1 

隐式参数

隐式参数指的是在函数或者方法中,定义一个用implicit修饰的参数,此时Scala会尝试找到一个指定类型的,用implicit修饰的对象即隐式值,如果找到就将其注入参数

Scala会在两个范围内查找:一种是当前作用域内可见的val或var定义的隐式变量;一种是隐式参数类型的伴生对象内的隐式值。 

案例:考试签到

class SignPen{ 
  def write(content: String) =println(content) 
} 
// 定义隐式参数 
implicit val signPen = new SignPen 
 
// 函数中使用隐式参数,此时scala会尝试找到一个指定SignPen类型的用implicit修饰的对象 
def signForExam(name: String) (implicit signPen: SignPen) { 
  signPen.write(name + " come to exam in time.") 
} 
 
defined class SignPen 
signPen: SignPen = SignPen@773dab28 
signForExam: (name: String)(implicit signPen: SignPen)Unit 
 
// 测试:当同学sparks来签到时,函数调用隐式参数的值完成签到 
scala> signForExam("sparks") 
sparks come to exam in time. 

Java数组与Scala数组的隐式转换

我们知道在Scala代码中,是直接可以调用JDK(JAVA)API的,比如调用一个Java数组类的方法,势必可能会传入Java类型的ArrayList,但是Scala中构造出来的其实是ArrayBuffer,你直接把Scala的ArrayBuffer传入Java接收ArrayList的方法,肯定不行。所以这时候隐式转换就派上用场了!

// 导入隐式转换函数,将scala的buffer类型转换为javalist类型 
import scala.collection.JavaConversions.bufferAsJavaList 
import scala.collection.mutable.ArrayBuffer 
 
// 测试 
val command = ArrayBuffer("javac", "C:\\Users\\Administrator\\Desktop\\Hello.java") 
 
val processBuilder = new ProcessBuilder(command) 
 
val process = processBuilder.start() 
// 转换成功 
val res = process.waitFor() 
res: Int = 2 
 
 
// 导入隐式转换函数,将javalist类型转换为scala的buffer类型 
import scala.collection.JavaConversions.asScalaBuffer 
 
import scala.collection.mutable.Buffer 
 
// 转换成功 
scala> val cmd: Buffer[String] = processBuilder.command() 
cmd: scala.collection.mutable.Buffer[String] = ArrayBuffer(javac, C:\Users\Administrator\Desktop\Hel 
lo.java) 

Java Map与Scala Map的隐式转换

//  Java Map转换为Scala Map 
import scala.collection.JavaConversions.mapAsScalaMap 
// 创建Java.util.HashMap对象 
val javaScores = new java.util.HashMap[String, Int]() 
 
javaScores.put("Alice", 10) 
javaScores.put("Bob", 3) 
javaScores.put("Cindy", 8) 
 
// 转换成功 
val scalaScorces: scala.collection.mutable.Map[String, Int] = javaScores 
scalaScorces: scala.collection.mutable.Map[String,Int] = Map(Bob -> 3, Alice -> 10, Cindy -> 8) 
 
 
//  Scala Map转换为Java Map 
import scala.collection.JavaConversions.mapAsJavaMap 
import java.awt.font.TextAttribute._ 
 
scala> val scalaAttrMap = Map(FAMILY -> "Sermi", SIZE -> 12) 
scalaAttrMap: scala.collection.immutable.Map[java.awt.font.TextAttribute,Any] = Map(java.awt.font.Te 
xtAttribute(family) -> Sermi, java.awt.font.TextAttribute(size) -> 12) 
 
// 转换成功 
scala> val font = new java.awt.Font(scalaAttrMap) 
font: java.awt.Font = java.awt.Font[family=Dialog,name=Sermi,style=plain,size=12]
posted @   小丑进场  阅读(785)  评论(2编辑  收藏  举报
编辑推荐:
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
阅读排行:
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· PPT革命!DeepSeek+Kimi=N小时工作5分钟完成?
· What?废柴, 还在本地部署DeepSeek吗?Are you kidding?
· DeepSeek企业级部署实战指南:从服务器选型到Dify私有化落地
· 程序员转型AI:行业分析
点击右上角即可分享
微信分享提示