1.1 隐式简介
1.2 隐式参数
1.3 隐式转换类型
1.4 隐式类
3.1 上界(Upper Bounds)和下届(Lower Bounds)
3.2 视图界定/上下文界定
正文
一,隐式(implicit)详解
1.1 隐式简介
思考: 我们调用别人的框架,发现少了一些方法,需要添加,但是让别人为你一个人添加是不可能滴。
比如使用 java.io.File 读取文件非常的繁琐,能不能让 Oracle 公司给我们再添加一个 read方法,直接返回文件中的所有内容,我想请问你脸大么?
伟大领袖毛主席教育我们说:“自己动手丰衣足食。”。
掌握 implicit 的用法是阅读 spark 源码的基础,也是学习 Scala 其它的开源框架的关键,
implicit 可分为:
1)隐式参数
2)隐式转换类型
3)隐式类
1.2 隐式参数
定义方法时,可以把参数列表标记为 implicit,表示该参数是隐式参数。一个方法只会有一个隐式参数列表,置于方法的最后一个参数列表。如果方法有多个隐式参数,只需一个implicit 修饰即可。
譬如:
def fire(x: Int)(implicit a:String, b: Int = 9527)
当调用包含隐式参数的方法是,如果当前上下文中有合适的隐式值,则编译器会自动为该组参数填充合适的值, 且上下文中只能有一个符合预期的隐式值。如果没有编译器会抛出异常。当然,标记为隐式参数的我们也可以手动为该参数添加默认值。
如下实例:
package implicitDemo1 object Demo1 { implicit val msg: String = "aaa" // 若定义两个 类型相同的则会报错 // implicit val msg2: String = "bbb" 如果加上这个代码就会报错 def say(implicit content: String): Unit ={ println(content) } def main(args: Array[String]): Unit = { // 这里调用方法say没有传参,但由于这里定义了了隐式参数 content // 且类型也为String,解释器就会找到已经定义好的隐式参数msg say // 输出 aaa } }
其他隐式参数注意事项:
package implicitDemo1 object Demo1 { implicit val msg: String = "aaa" implicit val age: Int = 1 //这里的定义方法有误,隐式参数必须写在参数列表后面这里写在前面 // 下面这种方法定义是错误的,因为隐式参数写在了前面 // def say(name: String, implicit content: String, age: Int){println(age)} // 下面是正确的,这样默认是两个隐式参数 def say(implicit content: String, age: Int): Unit ={ println(content) // aaa println(age) // 1 } def main(args: Array[String]): Unit = { say } }
1.3 隐式转换类型
package implicitDemo1 object Demo2 { implicit def doubleToInt(double: Double): Int ={ double.toInt } def main(args: Array[String]): Unit = { // 正常情况想这行代码报错 // 若在添加上述的doubleToInt的隐式方法则捕获报错 // 原因:此刻编译器会在当前上下文中找一个隐式转换,找一个能把浮点型变成Int的隐式转换 var money: Int = 20.5 print(money) // 输出20 } }
1.4 隐式类
有类Student:
package implicitDemo1 class Student{ def get(): Unit ={ print("aaa") } }
隐式类的使用:
package implicitDemo1 object Demo3 { // 定义一个隐式类,注意隐式类必须在静态对象内定义才有效 // 有点类似继承后添加了一个方法不同的是不需要用隐式类的列名调用, implicit class NewStudent(student: Student){ def set(): Unit ={ print("set") } } def main(args: Array[String]): Unit = { val student = new Student() student.get() student.set() // 这个是在隐式类定义的方法,在原来的类中没有,仍然可以调用 } }
二,泛型
通俗的讲,比如需要定义一个函数,函数的参数可以接受任意类型。我们不可能一一列举所有的参数类型重载函数。那么程序引入了一个称之为泛型的东西,这个类型可以代表任意的数据类型。例如 List,在创建 List 时,可以传入整形、字符串、浮点数等等任意类型。那是因为 List 在类定义时引用了泛型
如下实例:
package FanXin import FanXin.Em.Em // Scala 枚举类型 object Em extends Enumeration { type Em = Value val 上衣 , 内衣 , 裤子 , 袜子 = Value } // 在 Scala 定义泛型用[T], s 为泛型的引用 abstract class Message[T](s: T) { def get: T = s } // 子类扩展的时候,约定了具体的类型 class StrMessage[String](msg: String) extends Message(msg) class IntMessage[Int](msg: Int) extends Message(msg) // 定义一个泛型类 class Clothes[A,B,C](val clothesType: A, var color: B, var size: C) object Test { def main(args: Array[String]): Unit = { val s = new StrMessage("i e hate u you !") val i = new IntMessage(100) println (s.get) // i e hate u you ! println (i.get) // 100 // 创建一个对象, 传入的参数为 Em, String, Int val c1 = new Clothes(Em. 内衣 , "Red", 36) c1.size = 38 println (c1.clothesType, c1.size) // (内衣,38) // new 的时候,指定类型。那么传入的参数,必须是指定的类型 val c2 = new Clothes[Em, String, String](Em. 上衣 , "黑色", "32B") println (c2.size)// 32B // 定义一个函数,可以获取各类 List 的中间位置的值 val list1 = List ( "a", "b", "c") val list2 = List (1,2,3,4,5,6) // 定义一个方法接收任意类型的 List 集合 def getData[T](l: List[T])={ l(l.length/2) } } }
三,类型约束
3.1 上界(Upper Bounds)和下届(Lower Bounds)
Upper Bounds:
在 Java 泛型里表示某个类型是 Test 类型的子类型,使用 extends 关键字:
<T extends Test> // 或用通配符的形式 <? extends Test>
这种形式也叫 upper bounds(上限或上界),同样的意思在 Scala 的写法为:
[T<:Test] // 或用统配符 [_<:Test]
如下实例:
package Bound // T 实现了 Comparable 接口 class CmpComm[T<:Comparable[T]](o1: T, o2: T){ def bigger = if(o1.compareTo(o2)>0) o1 else o2 } object UpBound { def main(args: Array[String]): Unit = { // 这里会报错,因为这里的1没有实现Comparable但是Integer实现了 // val cmp = new CmpComm(1, 2) // 索引这里没有问题 val cmp1 = new CmpComm(Integer.valueOf(1), Integer.valueOf(2)) val bigger = cmp1.bigger print(bigger) // 2 } }
3.2 视图界定/上下文界定
视图界定:
<% 的意思是“view bounds”(视界),它比<: 适用的范围更广,除了所有的子类型,还允许隐式转换过去的类型。
package Bound // 如下:用 <: 在传参数的时候需要的参数类型是Ordered的类型或实现该类型的接口 // 对比上下届这里就有了局限性 //class CmpComm[T<:Ordered[T]](o1: T, o2: T){ // def bigger = if(o1.compareTo(o2)>0) o1 else o2 //} // 用视图界定, 传递的参数会发生类型的隐式转换,所以可以不必直接传Ordered类型的参数 class CmpComm1[T <% Ordered[T]](o1: T, o2: T){ def bigger = if(o1.compareTo(o2)>0) o1 else o2 } object ViewBound { def main(args: Array[String]): Unit = { // 1:错误 // val cmp = new CmpComm(Integer.valueOf(1), Integer.valueOf(2)) // val bigger = cmp.bigger // print(bigger) // 2:下面两种都可以,因为默认的进行了类型转换 val cmp1 = new CmpComm1(1, 2) // val cmp1 = new CmpComm1(Integer.valueOf(1), Integer.valueOf(2)) val bigger1 = cmp1.bigger print(bigger1) } }
上下文界定:
与 view bounds 一样 context bounds(上下文界定)也是隐式参数的语法糖。为语法上的方便,引入了”上下文界定”这个概念。上下文界定利用的是函数的柯里化来实现的。
代码实例:
package Bound /* * Comparator 传递比较器 * 有以下3中方式 * */ class CmpComm[T:Ordering](o1: T, o2: T)(implicit cmp: Ordering[T]){ def bigger = if(cmp.compare(o1, o2) > 0) o1 else o2 } class CmpComm1[T:Ordering](o1: T, o2: T){ def bigger = { var cmp = implicitly[Ordering[T]] if(cmp.compare(o1, o2) > 0) o1 else o2 } } class CmpComm2[T:Ordering](o1: T, o2: T){ def bigger = { def inner(implicit cmp: Ordering[T]) = cmp.compare(o1, o2) if (inner > 0) o1 else o2 } } object UpDownBound { def main(args: Array[String]): Unit = { val cmp1 = new CmpComm(1, 2) val big1 = cmp1.bigger val cmp2 = new CmpComm1(1, 2) val big2 = cmp2.bigger val cmp3 = new CmpComm2(1, 2) val big3 = cmp3.bigger print(big1) // 2 print(big2) // 2 print(big3) // 2 } }