上下文定界与implicitly方法

implicitly是在scala.Predef中定义的方法,它的方法签名是:

def implicitly[T](implicit e: T): T

它的返回值应该就是隐式参数e,即

def implicitly[T](implicit e: T): T = e

证明如下:

class Obj
implicit val obj:Obj=new Obj //Obj类型的隐式值obj

obj.hashCode  //obj哈希是194277435

implicitly[Obj].hashCode //调用implicitly方法,其返回值的哈希也是194277435

下面再看一个定义:

class One[T](val n:T)
def show[T:One](s:T)={}

定义了show,注意它的泛型是[T:One],这既不是上界也不是下界的写法,并且此时One也没有带它的类型(如[T:One[P]),这叫做上下文定界
show的类型是:

[T](s: T)(implicit evidence$1: One[T]) => Unit

可见show方法的类型是T,显然和One没有关系,并且有两个参数列表,第一个参数列表就是T类型的参数;而注意到第二个参数列表中是一个隐式参数evidence$1,它的类型才是One[T]
我们可以在方法中使用这个叫做evidence$1的参数:

def show[T:One](s:T)={
    println(evidence$1.n);
    print(s)
}
implicit val one=new One[Int](100)
show(10)  //输出100、10
show(10)(one) //输出100、10

虽然如此,但是因为这个隐式参数的名称是如此的奇怪,很明显这样的设计不期望我们直接使用这个隐式参数,也就是我们可以间接使用它。我们已经知道如果方法具有隐式参数,那么它将在方法内具有视界,很显然如果在方法中使用implicitly方法,那么该方法会捕获隐式参数evidence$1并直接返回,其结果就是,在具有上下文定界的方法内调用implicitly方法其实就是在使用evidence$1

既然有evidence$1,那么是否有evidence$2呢?答案肯定的,假设我们又定义了:

class Two[T](val  n:T)

那么:

scala>  def show[T:One,S:Two](a:T,b:S)={???}
show: [T, S](a: T, b: S)(implicit evidence$1: One[T], implicit evidence$2: Two[S])Nothing

implicitly方法根据类型来获取这些隐式值:

def show[T:One,S:Two]()={
      println(s"evidence$$1 --One.n = ${implicitly[One[T]].n}")
      println(s"evidence$$2 --Two.n = ${implicitly[Two[S]].n}")
}

implicit val (one,two)=(new One[Int](100),new Two[String]("Hello"))

show()
//输出:
evidence$1 --One.n = 100
evidence$2 --Two.n = Hello

我们接下来比较下面的方法签名:

scala> def show[T <% One](s:T){}
:22: error: class One takes type parameters
       def show[T <% One](s:T){}
                     ^
scala> def show[T : One[T]](s:T){}
:22: error: One[T] does not take type parameters
       def show[T : One[T]](s:T){}
                  ^
scala> def show[T <% One[K]](s:T){}
:22: error: not found: type K
       def show[T <% One[K]](s:T){}
                         ^

scala> def show[K,T <% One[K]](s:T){}
show: [K, T](s: T)(implicit evidence$1: T => One[K])Unit

scala> def show[T <% One[T]](s:T){}
show: [T](s: T)(implicit evidence$1: T => One[T])Unit

scala> def show[T : One](s:T){}
show: [T](s: T)(implicit evidence$1: One[T])Unit

如果使用视界<%,则必须指定类型(例如,T<%One就会报错),隐式参数的类型是T=>One[T]也就是函数类型,因此可以实现隐式转换!,并且可以指定不同的类型,如T=>One[K]

但是如果是使用定界:,那么隐式参数类型是One[T],且只能是One[T]

如果我们在视界中使用implicitly,如:

def show[T <% One[T]](s:T)={
    //此时具有隐式参数 evidence$1 ,类型是 T=>One[T]
    println{  //注意这个花括号
        //获取到这个隐式参数evidence$1
        val f=implicitly[T=>One[T]]
        f(s).n  //f(s)将返回一个One[T],获取到它的n
    }
}

implicit def intToOne(n:Int)=new One[Int](n)

show(100)  //输出100

注意上面println后跟一个花括号,在scala中花括号是一个表达式,里面可以很多语句,并返回最后一个值。又方法只有一个参数时可以用花括号代替圆括号,所以可以这么做。
上面我们将implicitly分开写,而没有直接写implicitly[T=>One](s).n,因为implicitly是一个方法,它会将s作为参数而不再寻找T=>One类型的隐式参数。

上面的写法过于繁琐,implicitly一般用于定界而不是视界,一般在方法中如果要是用隐式转换,我们就是用视界:

class One[T](val s:T){
    def ++++(other:T)="string-concat"+s+other
}

implicit def intToOne(i:Int)=new One[Int](i)

def show[T <% One[T]](s:T,other:T)={
    println(s++++other)
}

show[Int](66,88) //输出string-concat6688

11、重载方法不能在相同位置放置默认参数

Scala中的方法参数可以带有默认值,并且我们可以取得这些默认值:

object C{
    def m(name:String,age:Int=18){
        println(s"name=$name,age=$age")
    }
}

scala> C.m$default$2
res0: Int = 18

并且不能再赋值:

scala> C.m$default$2=15
:12: error: value m$default$2_= is not a member of object C
       C.m$default$2=15
         ^

因此,如果类C或对象O具有的method方法,而该方法的第n个参数具有默认值,那么我们可以获取它:new C(/*...*/).method$default$nO.method$default$n,也就是<宿主>.<方法名> $ default $ 参数位置
显然,如果类中有两个同名方法,并且在参数列表的同一位置(无论类型是否一致)都有默认参数,那么<宿主>.<方法名> $ default $ 参数位置就会出现二义性,所以重载方法不能在相同位置放置默认参数

class C{
    def m(name:String,age:Int=18){
        println(s"name=$name,age=$age")
    }
    def m(name:String,income:Double=2500.18){
        println(s"name=$name,income=$income")
    }
}
:11: error: in class C, multiple overloaded alternatives of 
      method m define default arguments.
       class C{
             ^

你可能感兴趣的:(上下文定界与implicitly方法)