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$n
或O.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{
^