【scala 笔记(9)】 隐式转换 和 隐式参数-- implicit

隐式转换和隐式参数是Scala的两个功能强大的工具, 在幕后处理那些很有价值的工作。

隐式转换

所谓隐式转换函数(implicit conversion function)指的是那种以implicit关键字声明的带有 单个参数 的函数。 正如它的名称所表达的, 这样的函数将被 自动应用 ,将值从 一种类型转换为另一种类型。

class Fraction(private var x:Int, private var y:Int){

  def * (other:Fraction) :Fraction = {
    new Fraction(this.x*other.x, this.y * other.y)
  }

  override def toString: String = "%s/%s".format(this.x, this.y)
}

object Fraction{
  // 隐式转换函数定义, 方法名通常命名为 source2Target
  implicit def int2Fraction(v :Int) :Fraction = new Fraction(v,1)
}

object Test {
  def main(args: Array[String]): Unit = {
    val f1 = new Fraction(1,3)
    val f2 = new Fraction(2,7)

    // 正常调用 * 
    println((f1 * f2).toString)
    // 通过隐式转换进行调用 int2Fraction(2) * f2
    println((2 * f2).toString)
  }
}
隐式函数将整数2转为一个Fraction对象,这个对象接着又被乘以f2.

你可以对转换函数任意命名,但不过建议采用 source2Target 这种约定的命名方式。

利用隐式转换丰富现有类库功能

你是否希望某个类中有某个方法, 而这个类的作者却没有提供, 例如 java.io.File类能有个read 方法来读取文件:

// 可惜在java 中不存在该方法
val content = new File("README.md").read 

在scala中 可以通过隐式转换进行类库功能方法添加:

class RichFile(val from: File){
    def read = Source.fromFile(from)
}

然后再提供一个隐式转换来将File变为RichFile:

implicit def file2RichFile(f:File) = new RichFile(f)

这样一来, 你就可以在File 对象上进行read方法调用。

引入隐式转换

Scala会考虑如下的隐式转换函数:
1. 位于源或目标类型的伴生对象中的隐式函数;
2. 位于当前作用域可以以单个标识符指代的隐式函数;

比如上述定义的int2Fraction函数,定义在了Fraction伴生对象中,这样它就能够用来将整数转换为分数。

假如Fraction 类和对象定义在 com.borey.scalautil包中。如果你想要使用必须像这样:

import com.borey.scalautil.Fraction._

隐式转换规则:

隐式转换在如下三种各不相同的情况会被考虑:

  • 当表达式的类型与预期的类型不同时;
  • 当对象访问一个不存在的成员时;
  • 当对象调用某个方法,而该方法的参数申明与传入参数不匹配时;

另一方面,也有三种情况编译器不会尝试使用隐式转换:
- 如果代码能够在不使用隐式转换的前提下通过编译,则不会使用隐式转换;
- 编译器不会尝试同时执行多个转换;
- 存在二义性的转换时个错误的; 例如 int2A(a) * b 和 int2B(a) * b 都是合法的, 编译器将会报错。

隐式参数

函数或方法可以带有一个标记为implicit的参数列表。这种情况下,编译器将会查找缺省值, 提供给该函数或方法。以下是一个简单的实例:

case class Delimiters(left:String, right:String)

def show(msg: String)(implicit delims:Delimiters) = {
    println(delims.left + msg + delims.right)
}

上述定义了一个柯里化函数 show 和 一个样例类Delimiters:

  • 显示调用
scala> show("hello borey")(Delimiters("{", "}"))
{hello borey}
  • 隐式调用
scala> implicit val show_delims = Delimiters("<", ">")
show_delims: Delimiters = Delimiters(<,>)

scala> show("hello borey")
<hello borey>

在这种情况下, 编译器将会查找一个类型为 Delimiters的隐式值。 这必须是一个被申明为implicit的值。 编译器将会在如下两个地方查找这样的一个对象:
1. 在当前作用域所有可以用单个标识符指代的满足类型要求的val 和 def。
2. 与所要求类型相关联的类型的伴生对象。相关联的类型包括要求类型本身以及它的类型参数(如果它是一个参数化的类型的话)

如果定义了两个类型为 Delimiters的隐式值 呢 ? 测试一下:
scala> implicit val show_delims = Delimiters("<", ">")
show_delims: Delimiters = Delimiters(<,>)

scala> implicit val show_delims2 = Delimiters("<<", ">>")
show_delims2: Delimiters = Delimiters(<<,>>)

scala> show("hello borey")
<console>:21: error: ambiguous implicit values:
 both value show_delims of type => Delimiters
 and value show_delims2 of type => Delimiters
 match expected type Delimiters
       show("hello borey")

我们可以看到,编译器会产生Error: 模糊不清的隐式值; 所以在使用的时候要谨慎些。

Scala程序隐式引入

每个Scala程序都隐式的以如下代码开始:

import java.lang._
import scala._
import Predef._

其实Predef对象中定义了大量常用的隐式转换, 感兴趣的可以去看下 Predef.scala源码。

在REPL中, 键入 :implicits 查看所有除Predef外被引入的隐式成员, 或者键入 :implicits -v 查看全部:

scala> :implicit
/* 1 implicit members imported from Fraction */
  /* 1 defined in Fraction */
  implicit def int2Fraction(v: Int): Fraction

scala> :implicit -v
/* 69 implicit members imported from scala.Predef */
  /* 7 inherited from scala */
  final implicit class ArrayCharSequence extends CharSequence
  final implicit class ArrowAssoc[A] extends AnyVal
  final implicit class Ensuring[A] extends AnyVal
  ......

  /* 40 inherited from scala.Predef */
  implicit def ArrowAssoc[A](self: A): ArrowAssoc[A]
  implicit def Ensuring[A](self: A): Ensuring[A]
  implicit def StringFormat[A](self: A): StringFormat[A]
  ......

  /* 22 inherited from scala.LowPriorityImplicits */
  implicit def genericWrapArray[T](xs: Array[T]): mutable.WrappedArray[T]
  implicit def wrapBooleanArray(xs: Array[Boolean]): mutable.WrappedArray[Boolean]
  implicit def wrapByteArray(xs: Array[Byte]): mutable.WrappedArray[Byte]
  ......

/* 1 implicit members imported from Fraction */
  /* 1 defined in Fraction */
  implicit def int2Fraction(v: Int): Fraction

Scala与Java集合的互操作

有时候, 你可能需要使用Java集合, 你多半会怀念Scala集合上那些丰富的处理方法。反过来讲, 你可能会想要构建出一个Scala集合, 然后传递给Java代码。

Scala 2.12.0 之前版本

JavaConversions 对象提供了用于在Scala和Java集合之间来回转换的一组方法。例如:

scala> import scala.collection.JavaConversions.mapAsScalaMap
import scala.collection.JavaConversions.mapAsScalaMap

scala> val b: scala.collection.mutable.Map[String,String] = System.getenv
<console>:15: warning: object JavaConversions in package collection is deprecated (since 2.12.0): use JavaConverters
       val b: scala.collection.mutable.Map[String,String] = System.getenv
                                                                   ^
b: scala.collection.mutable.Map[String,String] = Map(PATH -> /home/prod/softwares/scala/scala-2.12.2/bin: ...

可以查看 JavaConversions.scalaWrapAsScalaWrapAsJava 特质定义了大量隐式转换用于Java和Scala集合之间。

/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2006-2016, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://www.scala-lang.org/           **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

package scala
package collection

import convert._

/** A variety of implicit conversions supporting interoperability between
 *  Scala and Java collections.
 *
 *  The following conversions are supported:
 *{{{
 *    scala.collection.Iterable       <=> java.lang.Iterable
 *    scala.collection.Iterable       <=> java.util.Collection
 *    scala.collection.Iterator       <=> java.util.{ Iterator, Enumeration }
 *    scala.collection.mutable.Buffer <=> java.util.List
 *    scala.collection.mutable.Set    <=> java.util.Set
 *    scala.collection.mutable.Map    <=> java.util.{ Map, Dictionary }
 *    scala.collection.concurrent.Map <=> java.util.concurrent.ConcurrentMap
 *}}}
 *  In all cases, converting from a source type to a target type and back
 *  again will return the original source object:
 *
 *{{{
 *    import scala.collection.JavaConversions._
 *
 *    val sl = new scala.collection.mutable.ListBuffer[Int]
 *    val jl : java.util.List[Int] = sl
 *    val sl2 : scala.collection.mutable.Buffer[Int] = jl
 *    assert(sl eq sl2)
 *}}}
 *  In addition, the following one way conversions are provided:
 *
 *{{{
 *    scala.collection.Seq         => java.util.List
 *    scala.collection.mutable.Seq => java.util.List
 *    scala.collection.Set         => java.util.Set
 *    scala.collection.Map         => java.util.Map
 *    java.util.Properties         => scala.collection.mutable.Map[String, String]
 *}}}
 *
 *  The transparent conversions provided here are considered
 *  fragile because they can result in unexpected behavior and performance.
 *
 *  Therefore, this API has been deprecated and `JavaConverters` should be
 *  used instead. `JavaConverters` provides the same conversions, but through
 *  extension methods.
 *
 *  @author Miles Sabin
 *  @author Martin Odersky
 *  @since  2.8
 */
@deprecated("use JavaConverters", since="2.12.0")
object JavaConversions extends WrapAsScala with WrapAsJava

Scala 2.12.0 之后版本

JavaConversions在2.12.0向后开始被废弃, 因为可能会导致不可预测的意外行为发生,建议采用JavaConverters 对象进行显式方法调用, 例如:

scala> import scala.collection.JavaConverters.mapAsScalaMap
import scala.collection.JavaConverters.mapAsScalaMap

scala> val b: scala.collection.mutable.Map[String,String] = mapAsScalaMap(System.getenv)
b: scala.collection.mutable.Map[String,String] = Map(PATH -> /h ...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值