隐式转换和隐式参数
This commit is contained in:
		| @@ -178,7 +178,7 @@ object Person { | |||||||
| + 写在主构造器中的代码块会在类初始化的时候被执行,功能类似于Java的静态代码块`static{}` | + 写在主构造器中的代码块会在类初始化的时候被执行,功能类似于Java的静态代码块`static{}` | ||||||
|  |  | ||||||
| ```scala | ```scala | ||||||
| class Person(name: String, age: Int) { | class Person(val name: String, val age: Int) { | ||||||
|  |  | ||||||
|   println("功能类似于Java的静态代码块static{}") |   println("功能类似于Java的静态代码块static{}") | ||||||
|  |  | ||||||
| @@ -210,7 +210,7 @@ heibaiying:20 | |||||||
| + 每个辅助构造器必须以主构造器或其他的辅助构造器的调用开始。 | + 每个辅助构造器必须以主构造器或其他的辅助构造器的调用开始。 | ||||||
|  |  | ||||||
| ```scala | ```scala | ||||||
| class Person(name: String, age: Int) { | class Person(val name: String, val age: Int) { | ||||||
|  |  | ||||||
|   private var birthday = "" |   private var birthday = "" | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										154
									
								
								notes/Scala类型参数.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								notes/Scala类型参数.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | |||||||
|  | # 类型参数 | ||||||
|  |  | ||||||
|  | ## 一、泛型 | ||||||
|  |  | ||||||
|  | Scala支持类型参数化,使得我们能够编写泛型程序。 | ||||||
|  |  | ||||||
|  | ### 1.1 泛型类 | ||||||
|  |  | ||||||
|  | Java中使用`<>`符号来定义类型参数,Scala中使用`[]`来定义类型参数。 | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | class Pair[T, S](val first: T, val second: S) { | ||||||
|  |   override def toString: String = first + ":" + second | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | object ScalaApp extends App { | ||||||
|  |  | ||||||
|  |   // 使用时候你直接指定参数类型,也可以不指定,由程序自动推断 | ||||||
|  |   val pair01 = new Pair("heibai01", 22) | ||||||
|  |   val pair02 = new Pair[String,Int]("heibai02", 33) | ||||||
|  |  | ||||||
|  |   println(pair01) | ||||||
|  |   println(pair02) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 1.2 泛型方法 | ||||||
|  |  | ||||||
|  | 函数和方法也支持类型参数。 | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | object Utils { | ||||||
|  |   def getHalf[T](a: Array[T]): Int = a.length / 2 | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## 二、类型限定 | ||||||
|  |  | ||||||
|  | ### 2.1 类型上界 | ||||||
|  |  | ||||||
|  | 对于对象之间进行大小比较,Scala和Java一样,都要求比较的对象需要实现`java.lang.Comparable`接口。 | ||||||
|  |  | ||||||
|  | 所以如果想对泛型进行比较,需要限定类型上界,语法为` S <: T`,代表S必须是类型T的子类或其本身。示例如下: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | // 使用 <: 符号,限定T必须是Comparable[T]的子类型 | ||||||
|  | class Pair[T <: Comparable[T]](val first: T, val second: T) { | ||||||
|  |   // 返回较小的值 | ||||||
|  |   def smaller: T = if (first.compareTo(second) < 0) first else second | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | // 测试代码 | ||||||
|  | val pair = new Pair("abc", "abcd") | ||||||
|  | println(pair.smaller) // 输出 abc | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | >注:如果你想要在Java中实现类型变量限定,需要使用关键字extends来实现,对于的Java代码如下: | ||||||
|  | > | ||||||
|  | >```java | ||||||
|  | >public class Pair<T extends Comparable<T>> { | ||||||
|  | >    private T first; | ||||||
|  | >    private T second; | ||||||
|  | >    Pair(T first, T second) { | ||||||
|  | >        this.first = first; | ||||||
|  | >        this.second = second; | ||||||
|  | >    } | ||||||
|  | >    public T smaller() { | ||||||
|  | >        return first.compareTo(second) < 0 ? first : second; | ||||||
|  | >    } | ||||||
|  | >} | ||||||
|  | >``` | ||||||
|  |  | ||||||
|  | ### 2.2 视图界定 & 类型约束 | ||||||
|  |  | ||||||
|  | #### 1.视图界定 | ||||||
|  |  | ||||||
|  | 在上面的例子中,如果你使用Int类型或者Double等类型进行测试,点击运行后,你会发现程序根本无法通过编译: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | val pair1 = new Pair(10, 12) | ||||||
|  | val pair2 = new Pair(10.0, 12.0) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 之所以出现这样的问题,是因为在Scala中Int并没有实现Comparable,真正实现Comparable接口的是RichInt。在日常的编程中之所以你能够执行`3>2`这样的判断操作,是因为程序执行了隐式转换,将Int转换为RichInt。 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 直接继承Java中Comparable接口的是特质Ordered,RichInt混入了该特质,Ordered源码如下: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | // 除了compareTo方法外,还提供了额外的关系符方法 | ||||||
|  | trait Ordered[A] extends Any with java.lang.Comparable[A] { | ||||||
|  |   def compare(that: A): Int | ||||||
|  |   def <  (that: A): Boolean = (this compare that) <  0 | ||||||
|  |   def >  (that: A): Boolean = (this compare that) >  0 | ||||||
|  |   def <= (that: A): Boolean = (this compare that) <= 0 | ||||||
|  |   def >= (that: A): Boolean = (this compare that) >= 0 | ||||||
|  |   def compareTo(that: A): Int = compare(that) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 所以要想在泛型中解决这个问题,需要使用视图界定: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | // 视图界定符号 <% | ||||||
|  | class Pair[T <% Comparable[T]](val first: T, val second: T) { | ||||||
|  |   // 返回较小的值 | ||||||
|  |   def smaller: T = if (first.compareTo(second) < 0) first else second | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | > 注:由于直接继承Java中Comparable接口的是特质Ordered,所以也可以使用如下的视图界定: | ||||||
|  | > | ||||||
|  | > ```scala | ||||||
|  | > class Pair[T <% Ordered[T]](val first: T, val second: T) { | ||||||
|  | >      | ||||||
|  | > def smaller: T = if (first.compareTo(second) < 0) first else second | ||||||
|  | >      | ||||||
|  | > } | ||||||
|  | > ``` | ||||||
|  |  | ||||||
|  | #### 2. 类型约束 | ||||||
|  |  | ||||||
|  | 如果你用的Scala是2.11+,则视图界定已经不推荐使用,官方推荐使用类型约束(type constraint)来实现同样的功能: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | class Pair[T](val first: T, val second: T)(implicit ev: T => Comparable[T]) { | ||||||
|  |      | ||||||
|  |   def smaller: T = if (first.compareTo(second) < 0) first else second | ||||||
|  |      | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 2.3 上下文界定 | ||||||
|  |  | ||||||
|  | 上下文界定的形式为`T:M`,它要求必须存在一个类型为M[T]的隐式值,当你声明一个使用隐式值的方法时,需要添加一个隐式参数。上面的程序也可以使用上下文界定进行如下改写: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | class Pair[T](val first: T, val second: T) { | ||||||
|  |      | ||||||
|  |   def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second | ||||||
|  |      | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 2.4 类型下界 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## 三、类型通配符 | ||||||
							
								
								
									
										358
									
								
								notes/Scala隐式转换和隐式参数.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										358
									
								
								notes/Scala隐式转换和隐式参数.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,358 @@ | |||||||
|  | # 隐式转换和隐式参数 | ||||||
|  |  | ||||||
|  | <nav> | ||||||
|  | <a href="#一隐式转换">一、隐式转换</a><br/> | ||||||
|  |         <a href="#11-使用隐式转换">1.1 使用隐式转换</a><br/> | ||||||
|  |         <a href="#12-隐式转换规则">1.2 隐式转换规则</a><br/> | ||||||
|  |         <a href="#13-引入隐式转换">1.3 引入隐式转换</a><br/> | ||||||
|  | <a href="#二隐式参数">二、隐式参数</a><br/> | ||||||
|  |         <a href="#21-使用隐式参数">2.1 使用隐式参数</a><br/> | ||||||
|  |         <a href="#22-引入隐式参数">2.2 引入隐式参数</a><br/> | ||||||
|  |         <a href="#23-利用隐式参数进行隐式转换">2.3 利用隐式参数进行隐式转换</a><br/> | ||||||
|  | </nav> | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## 一、隐式转换 | ||||||
|  |  | ||||||
|  | ### 1.1 使用隐式转换 | ||||||
|  |  | ||||||
|  | 隐式转换指的是以`implicit`关键字声明带有单个参数的转换函数,它将值从一种类型转换为另一种类型,以便使用之前类型所没有的功能。示例如下: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | // 普通人 | ||||||
|  | class Person(val name: String) | ||||||
|  |  | ||||||
|  | // 雷神 | ||||||
|  | class Thor(val name: String) { | ||||||
|  |   // 正常情况下只有雷神才能举起雷神之锤 | ||||||
|  |   def hammer(): Unit = { | ||||||
|  |     println(name + "举起雷神之锤") | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | object Thor extends App { | ||||||
|  |   // 定义隐式转换方法 将普通人转换为雷神 通常建议方法名使用source2Target,即:被转换对象To转换对象 | ||||||
|  |   implicit def person2Thor(p: Person): Thor = new Thor(p.name) | ||||||
|  |   // 这样普通人也能举起雷神之锤 | ||||||
|  |   new Person("普通人").hammer() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 输出: 普通人举起雷神之锤 | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | <br/> | ||||||
|  |  | ||||||
|  | ### 1.2 隐式转换规则 | ||||||
|  |  | ||||||
|  | 并不是你使用`implicit`转换后,隐式转换就一定会发生,比如上面如果不调用`hammer()`方法的时候,普通人就还是普通人。通常程序会在以下情况下尝试执行隐式转换: | ||||||
|  |  | ||||||
|  | + 当对象访问一个不存在的成员时,即调用的方法不存在或者访问的成员变量不存在; | ||||||
|  | + 当对象调用某个方法,该方法存在,但是方法的声明参数与传入参数不匹配时。 | ||||||
|  |  | ||||||
|  | 而在以下三种情况下编译器不会尝试执行隐式转换: | ||||||
|  |  | ||||||
|  | + 如果代码能够在不使用隐式转换的前提下通过编译,则不会使用隐式转换; | ||||||
|  | + 编译器不会尝试同时执行多个转换,比如`convert1(convert2(a))*b`; | ||||||
|  | + 转换存在二义性,也不会发生转换。 | ||||||
|  |  | ||||||
|  | 这里首先解释一下二义性,上面的代码进行如下修改,由于两个隐式转换都是生效的,所以就存在了二义性: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | //两个隐式转换都是有效的 | ||||||
|  | implicit def person2Thor(p: Person): Thor = new Thor(p.name) | ||||||
|  | implicit def person2Thor2(p: Person): Thor = new Thor(p.name) | ||||||
|  | // 此时下面这段语句无法通过编译 | ||||||
|  | new Person("普通人").hammer() | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 其次再解释一下多个转换的问题: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | class ClassA { | ||||||
|  |   override def toString = "This is Class A" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ClassB { | ||||||
|  |   override def toString = "This is Class B" | ||||||
|  |   def printB(b: ClassB): Unit = println(b) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ClassC | ||||||
|  |  | ||||||
|  | class ClassD | ||||||
|  |  | ||||||
|  | object ImplicitTest extends App { | ||||||
|  |   implicit def A2B(a: ClassA): ClassB = { | ||||||
|  |     println("A2B") | ||||||
|  |     new ClassB | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   implicit def C2B(c: ClassC): ClassB = { | ||||||
|  |     println("C2B") | ||||||
|  |     new ClassB | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   implicit def D2C(d: ClassD): ClassC = { | ||||||
|  |     println("D2C") | ||||||
|  |     new ClassC | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // 这行代码无法通过编译,因为要调用到printB方法,需要执行两次转换C2B(D2C(ClassD)) | ||||||
|  |   new ClassD().printB(new ClassA) | ||||||
|  |      | ||||||
|  |   /* | ||||||
|  |    *  下面的这一行代码虽然也进行了两次隐式转换,但是两次的转换对象并不是一个对象,所以它是生效的: | ||||||
|  |    *  转换流程如下: | ||||||
|  |    *  1. ClassC中并没有printB方法,因此隐式转换为ClassB,然后调用printB方法; | ||||||
|  |    *  2. 但是printB参数类型为ClassB,然而传入的参数类型是ClassA,所以需要将参数ClassA转换为ClassB,这是第二次; | ||||||
|  |    *  即: C2B(ClassC) -> ClassB.printB(ClassA) -> ClassB.printB(A2B(ClassA)) -> ClassB.printB(ClassB) | ||||||
|  |    *  转换过程1的对象是ClassC,而转换过程2的转换对象是ClassA,所以虽然是一行代码两次转换,但是仍然是有效转换 | ||||||
|  |    */ | ||||||
|  |   new ClassC().printB(new ClassA) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 输出: | ||||||
|  | C2B | ||||||
|  | A2B | ||||||
|  | This is Class B | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | <br/> | ||||||
|  |  | ||||||
|  | ### 1.3 引入隐式转换 | ||||||
|  |  | ||||||
|  | 隐式转换的可以定义在以下三个地方: | ||||||
|  |  | ||||||
|  | + 定义在原类型的伴生对象中; | ||||||
|  | + 直接定义在执行代码的上下文作用域中; | ||||||
|  | + 统一定义在一个文件中,在使用时候导入。 | ||||||
|  |  | ||||||
|  | 上面我们使用的方法相当于直接定义在执行代码的作用域中,下面分别给出其他两种定义的代码示例: | ||||||
|  |  | ||||||
|  | 定义在原类型的伴生对象中: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | class Person(val name: String) | ||||||
|  | // 在伴生对象中定义隐式转换函数 | ||||||
|  | object Person{ | ||||||
|  |   implicit def person2Thor(p: Person): Thor = new Thor(p.name) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | class Thor(val name: String) { | ||||||
|  |   def hammer(): Unit = { | ||||||
|  |     println(name + "举起雷神之锤") | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | // 使用示例 | ||||||
|  | object ScalaApp extends App { | ||||||
|  |   new Person("普通人").hammer() | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 定义在一个公共的对象中: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | object Convert { | ||||||
|  |   implicit def person2Thor(p: Person): Thor = new Thor(p.name) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | // 导入Convert下所有的隐式转换函数 | ||||||
|  | import com.heibaiying.Convert._ | ||||||
|  |  | ||||||
|  | object ScalaApp extends App { | ||||||
|  |   // 这样普通人也能举起雷神之锤 | ||||||
|  |   new Person("普通人").hammer() | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | > 注:Scala中隐式转换函数大部分定义在`Predef.scala`中,你可以打开源文件查看,也可以在Scala交互式命令行中采用`:implicit -v`查看全部隐式转换函数。 | ||||||
|  |  | ||||||
|  | <br/> | ||||||
|  |  | ||||||
|  | ## 二、隐式参数 | ||||||
|  |  | ||||||
|  | ### 2.1 使用隐式参数 | ||||||
|  |  | ||||||
|  | 函数或方法可以带有一个标记为`implicit`的参数列表,这种情况下,编译器将会查找默认值,提供给函数调用。 | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | // 定义分隔符类 | ||||||
|  | class Delimiters(val left: String, val right: String) | ||||||
|  |  | ||||||
|  | object ScalaApp extends App { | ||||||
|  |    | ||||||
|  |     // 进行格式化输出 | ||||||
|  |   def formatted(context: String)(implicit deli: Delimiters): Unit = { | ||||||
|  |     println(deli.left + context + deli.right) | ||||||
|  |   } | ||||||
|  |      | ||||||
|  |   // 定义一个隐式默认值 使用左右中括号作为分隔符 | ||||||
|  |   implicit val bracket = new Delimiters("(", ")") | ||||||
|  |   formatted("this is context") // 输出: (this is context) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 关于隐式参数,有两点需要注意: | ||||||
|  |  | ||||||
|  | 1.我们上面定义`formatted`函数的时候使用了柯里化,如果你不使用柯里化表达式,按照通常习惯只有下面两种写法: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | //这种写法没有语法错误,但是无法通过编译 | ||||||
|  | def formatted(implicit context: String, deli: Delimiters): Unit = { | ||||||
|  |   println(deli.left + context + deli.right) | ||||||
|  | }  | ||||||
|  | // 不存在这种写法,IDEA直接会直接提示语法错误 | ||||||
|  | def formatted( context: String,  implicit deli: Delimiters): Unit = { | ||||||
|  |   println(deli.left + context + deli.right) | ||||||
|  | }  | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 上面第一种写法编译的时候会出现下面所示`error`信息,从中也可以看出`implicit`是作用于参数列表中每个参数的,这显然不是我们想要到达的效果,所以上面的写法采用了柯里化。 | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | not enough arguments for method formatted: (implicit context: String, implicit deli: com.heibaiying.Delimiters) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 2.第二个问题和隐式函数一样,隐式默认值不能存在二义性,否则无法通过编译,示例如下: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | implicit val bracket = new Delimiters("(", ")") | ||||||
|  | implicit val brace = new Delimiters("{", "}") | ||||||
|  | formatted("this is context") | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 上面代码无法通过编译,出现错误提示`ambiguous implicit values`,即隐式值存在冲突。 | ||||||
|  |  | ||||||
|  | <br/> | ||||||
|  |  | ||||||
|  | ### 2.2 引入隐式参数 | ||||||
|  |  | ||||||
|  | 引入隐式参数和引入隐式转换函数方法是一样的,有以下三种方式: | ||||||
|  |  | ||||||
|  | - 定义在隐式参数对应类的伴生对象中; | ||||||
|  | - 直接定义在执行代码的上下文作用域中; | ||||||
|  | - 统一定义在一个文件中,在使用时候导入。 | ||||||
|  |  | ||||||
|  | 我们上面示例程序相当于直接定义执行代码的上下文作用域中,下面给出其他两种方式的示例: | ||||||
|  |  | ||||||
|  | 定义在隐式参数对应类的伴生对象中; | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | class Delimiters(val left: String, val right: String) | ||||||
|  |  | ||||||
|  | object Delimiters { | ||||||
|  |   implicit val bracket = new Delimiters("(", ")") | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | // 此时执行代码的上下文中不用定义 | ||||||
|  | object ScalaApp extends App { | ||||||
|  |  | ||||||
|  |   def formatted(context: String)(implicit deli: Delimiters): Unit = { | ||||||
|  |     println(deli.left + context + deli.right) | ||||||
|  |   } | ||||||
|  |   formatted("this is context")  | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 统一定义在一个文件中,在使用时候导入: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | object Convert { | ||||||
|  |   implicit val bracket = new Delimiters("(", ")") | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | // 在使用的时候导入 | ||||||
|  | import com.heibaiying.Convert.bracket | ||||||
|  |  | ||||||
|  | object ScalaApp extends App { | ||||||
|  |   def formatted(context: String)(implicit deli: Delimiters): Unit = { | ||||||
|  |     println(deli.left + context + deli.right) | ||||||
|  |   } | ||||||
|  |   formatted("this is context") // 输出: (this is context) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | <br/> | ||||||
|  |  | ||||||
|  | ### 2.3 利用隐式参数进行隐式转换 | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | def smaller[T] (a: T, b: T) = if (a < b) a else b | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 在Scala中如果定义了一个如上所示的比较对象大小的泛型方法,你会发现无法通过编译。对于对象之间进行大小比较,Scala和Java一样,都要求被比较的对象需要实现java.lang.Comparable接口。 | ||||||
|  |  | ||||||
|  | 在Scala中,直接继承Java中Comparable接口的是特质Ordered,它在继承compareTo方法的基础上,额外定义了关系符方法,源码如下: | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | trait Ordered[A] extends Any with java.lang.Comparable[A] { | ||||||
|  |   def compare(that: A): Int | ||||||
|  |   def <  (that: A): Boolean = (this compare that) <  0 | ||||||
|  |   def >  (that: A): Boolean = (this compare that) >  0 | ||||||
|  |   def <= (that: A): Boolean = (this compare that) <= 0 | ||||||
|  |   def >= (that: A): Boolean = (this compare that) >= 0 | ||||||
|  |   def compareTo(that: A): Int = compare(that) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 所以要想在泛型中解决这个问题,有两种方法: | ||||||
|  |  | ||||||
|  | #### 1. 使用视图界定 | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | object Pair extends App { | ||||||
|  |  | ||||||
|  |  // 视图界定 | ||||||
|  |   def smaller[T<% Ordered[T]](a: T, b: T) = if (a < b) a else b | ||||||
|  |   | ||||||
|  |   println(smaller(1,2)) //输出 1 | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 视图限定限制了T可以通过隐式转换`Ordered[T]`,即对象一定可以进行大小比较。在上面的代码中`smaller(1,2)`中参数`1`和`2`实际上是通过定义在`Predef`中的隐式转换方法`intWrapper`转换为`RichInt`。 | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | // Predef.scala | ||||||
|  | @inline implicit def intWrapper(x: Int)   = new runtime.RichInt(x) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 为什么要这么麻烦执行隐式转换,原因是Scala中的Int类型并不能直接进行比较,因为其没有实现`Ordered`特质,真正实现`Ordered`特质的是`RichInt`。 | ||||||
|  |  | ||||||
|  | <div align="center"> <img  src="https://github.com/heibaiying/BigData-Notes/blob/master/pictures/scala-richInt.png"/> </div> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #### 2. 利用隐式参数进行隐式转换 | ||||||
|  |  | ||||||
|  | 在Scala2.11+后,视图界定被标识为废弃,官方推荐使用类型限定来解决上面的问题,本质上就是使用隐式参数进行隐式转换。 | ||||||
|  |  | ||||||
|  | ```scala | ||||||
|  | object Pair extends App { | ||||||
|  |  | ||||||
|  |    // order既是一个隐式参数也是一个隐式转换,即如果a不存在 < 方法,则转换为order(a)<b | ||||||
|  |   def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]) = if (a < b) a else b | ||||||
|  |  | ||||||
|  |   println(smaller(1,2)) //输出 1 | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## 参考资料 | ||||||
|  |  | ||||||
|  | 1. Martin Odersky . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1   | ||||||
|  | 2. 凯.S.霍斯特曼  . 快学Scala(第2版)[M] . 电子工业出版社 . 2017-7 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								pictures/scala-richInt.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pictures/scala-richInt.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 11 KiB | 
							
								
								
									
										
											BIN
										
									
								
								pictures/scala-视图界定.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								pictures/scala-视图界定.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 20 KiB | 
		Reference in New Issue
	
	Block a user