scala(7)隐式转换 隐式变量 隐式类

Scala提供的隐式转换特性可以在效果上给一个类增加一些方法,或者用于接收不同类型的对象.

implicit关键字只能用来修饰方法、变量(参数)和伴随对象。
隐式转换的方法(变量和伴随对象)在当前范围内才有效。如果隐式转换不在当前范围内定义(比如定义在另一个类中或包含在某个对象中),那么必须通过import语句将其导。举例

package demo {
package util {

import java.util.Date
import java.text.SimpleDateFormat

object 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.Rest
   def 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 YourTest
    myTest.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 scope
  def printInt(i: Int) = printStr(i)

  def printYourTest(obj: YourTest) = println(obj)

  def getYorTest(): YourTest = this;
}

object MyTest {
  implicit def int2String(i: Int ): String = i.toString
  implicit def myTest2YourTest(obj: MyTest): YourTest= new YourTest
// implicit val myTest2YourTest = (obj : MyTest) => new YourTest
}
}  

这样规定的好处是,通过限制隐式转换的有效范围,使维护和理解代码变得相对容易.
一般来说,scala编译器会首先在方法调用处的当前范围内查找隐式转换函数;如果没有找到,会尝试在源类型或目标类型(包括源类型和目标类型的类型变量的类型)的伴随对象中查找转换函数,如果还是没找到,则拒绝编译。

比如:

object ABCDMain extends App {

  class B

  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转换函数将new D()转换成C类, 然后调用C类的printC方法;但发现传入的参数类型是B类,于是搜索当前范围有无合适的转换函数,发现B2C转换函数符合要求。

又比如:

object ABCDMain extends App {
  class A

  object 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 D

  implicit 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 A

  object A{
  //implicit def MA2MC(ma: M[A]) ={
  // new M[C];
  //}
  }

  class C

  object 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 C

def 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 { 
implicit def str2int(implicit s: String,l: List[Int]): Int = { 
println("str2int") 
Integer.parseInt(s) 
}
implicit def user2PrintOps(s: String) = { 
println("use2PrintOps") 
new PrintOps 
}


implicit def getString = { 
println("getString") 
"123"
}

implicit def newList = { 
println("newList") 
List(2) 
}
class PrintOps() { 
def print(implicit i: Int) = println(i); 
}

"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中会发生怎样转换?若搞不明白,请参考https://gist.github.com/1388106
笔者觉得,如没必要尽量少用隐式转换,毕竟太“魔幻”的代码理解起来还是要费点劲。

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 + i2
匿名函数不能声明隐式参数,即不能这样写:

val f = (implicit s: String) => s+1

如果一个函数带有implicit参数,则无法通过 _ 得到该函数引用。你尝试这样做是无法编译的:

def maxFunc(implicit i1: Int, i2: Int) = i1 + i2
val f = maxFunc _ // 编译错误
可以给匿名函数的参数加上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

隐式参数
参数用implicit修饰的参数
作用一:
有一种方式你可以申明最后一个参数为隐式(implicit),不需要显式的传递。语法如下:

scala> def speakImplicitly (implicit greeting : String) = println(greeting)
speakImplicitly: (implicit String)Unit

scala> speakImplicitly("Goodbye world")
Goodbye world

scala> speakImplicitly
:6: error: no implicit argument matching parameter type String was foud.

scala> implicit val hello = "Hello world"
hello: java.lang.String = Hello world

scala> speakImplicitly
Hello world

你可以正常调用,也可以不传参数,系统将从作用域中找到一个被标示为
implicit变量,如果找不到implicit变量,编译的时候将会报错。

隐式变量的查找是强制类型匹配的,下面是一些例子展现:
错误的类型:

scala> def speakImplicitly (implicit greeting : String) = println(greeting)
speakImplicitly: (implicit String)Unit

scala> implicit val aUnit = ();
aUnit: Unit = ()

scala> speakImplicitly
:7: error: no implicit argument matching parameter type String was found.
只有String类型的隐式变量才能够匹配。

错误的泛型:
scala> def speakImplicitly (implicit greeting : String) = println(greeting)
speakImplicitly: (implicit String)Unit

scala> implicit val hello : Any = "Hello world"
hello: Any = Hello world

scala> speakImplicitly
:7: error: no implicit argument matching parameter type String was found.
隐式参数匹配必须是静态类型,泛型不能够匹配。

多个相同类型的参数:
scala> def speakImplicitly (implicit greeting : String) = println(greeting)
speakImplicitly: (implicit String)Unit

scala> implicit val foo = "foo";
foo: java.lang.String = foo

scala> implicit val bar = "bar";
bar: java.lang.String = bar

scala> speakImplicitly
:9: error: ambiguous implicit values:
 both value bar in object $iw of type => java.lang.String
 and value foo in object $iw of type => java.lang.String
 match expected type String
系统找到两个隐式参数是相同的类型,将无法识别。

子类可以匹配,
scala> def sayThings (implicit args : List[Any]) = args.foreach(println(_)) sayThings: (implicit List[Any])Unit scala> implicit val nothingNiceToSay : List[Any] = Nil nothingNiceToSay: List[Any] = List() scala> sayThings scala> implicit val hellos : List[String] = List("Hello world"); hellos: List[String] = List(Hello world) scala> sayThings Hello world

String作为Any的子类,所以系统可以匹配到.

作用二:

def smaller[T](first: T, second: T)(implicit sorted:T -> Ordered[T])={
 if (first<second) first else second 
}

隐式类
注意:隐式类必须定义在的一个class 或者object或者trait中,主构造器中只能带有一个不是implicit的参数,隐式类中不能有同名。

implicit class Implicit(p:Int){
    def add =p+2
}
println(1.add)

你可能感兴趣的:(scala,Class)