四、Scala之隐世转换与隐世参数

Scala之隐世转换与隐世参数

文章目录

1. 隐世转换入门

先看一段代码,引出隐式转换的实际需要=>指定某些数据类型的相互转化

1
2
3
4
5
6
object Scala01 {
def main(args: Array[String]): Unit = {
val num : Int = 3.5 //?错 高精度->低精度
println(num)
}
}

隐式函数基本介绍

隐式转换函数是以implicit关键字声明的带有单个参数的函数。这种函数将会自动应用,将值从一种类型转换为另一种类型

使用隐式函数可以优雅的解决数据类型转换,以前面的案例进行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package implicitPack

object T1 {

def main(args: Array[String]): Unit = {
implicit def f1(d: Double): Int = {
d.toInt
}
implicit def f2(l: Long): Int = {
l.toInt
}
val num: Int = 3.5
println(num)
val num2: Int = 4.5
println(num2)
val num3: Int = 20l
}
}

隐式转换的注意事项和细节

  1. 隐式转换函数的函数名可以是任意的,隐式转换与函数名称无关,只与函数签名(函数参数类型和返回值类型)有关。

  2. 隐式函数可以有多个(即:隐式函数列表),但是需要保证在当前环境下,只有一个隐式函数能被识别

1
2
3
4
5
//在当前环境中,不能存在满足条件的多个隐式函数
implicit def a(d: Double) = d.toInt
implicit def b(d: Double) = d.toInt
val i1: Int = 3.5 //(X)在转换时,识别出有两个方法可以被使用,就不确定调用哪一个,所以出错
println(i1)

2. 隐式方法

如果需要为一个类增加一个方法,可以通过隐式转换来实现。(动态增加功能)比如想为MySQL类增加一个delete方法

分析解决方案

在当前程序中,如果想要给MySQL类增加功能是非常简单的,但是在实际项目中,如果想要增加新的功能就会需要改变源代码,这是很难接受的。而且违背了软件开发的OCP开发原则 (闭合原则 open close priceple)。在这种情况下,可以通过隐式转换函数给类动态添加功能。

使用隐式转换方式动态的给MySQL类增加delete方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package implicitPack

object T2 {

def main(args: Array[String]): Unit = {
//这个形参就是自动匹配用的
implicit def addDelete(mysql: MySQL): DB = {////隐世方法不一定写在main方法里,只要在作用域范围内能找到就行
new DB //
}

val mysql = new MySQL
mysql.insert()//输出insert
mysql.delete()//输出delete
}
}

class MySQL {
def insert(): Unit = {
println("insert")
}
}

class DB {
def delete(): Unit = {
println("delete")
}
}

这个代码经过反编译后为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final class T2$ {
public static T2$ MODULE$;

private static final DB addDelete$1(MySQL mysql) {
return new DB();
}

public void main(String[] args) {
MySQL mysql = new MySQL();
mysql.insert();
addDelete$1(mysql).delete();
}

private T2$() {
MODULE$ = this;
}
}

3. 隐式值

隐式值也叫隐式变量,将某个形参变量标记为implicit,所以编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺省参数

应用案例

1
2
3
4
5
6
7
8
9
10
11
12
package implicitPack

object T3 {

def main(args: Array[String]): Unit = {
implicit val str1: String = "jack" //隐世值不一定写在main方法里,只要在作用域范围内能找到就行
def hello(implicit name: String): Unit = {
println(name + " hello")
}
hello //调用.不带(),输出jack hello
}
}

使用隐式值时,不要出现模棱两可的现象,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
package implicitPack

object T4 {
def main(args: Array[String]): Unit = {
// 隐式变量(值)
implicit val name: String = "Scala"
implicit val name1: String = "World"
def hello(implicit content: String = "jack"): Unit = {
println("Hello " + content)
} //调用hello
hello
}
}

会保存,信息如下:

1
2
3
4
5
ambiguous implicit values:
both value name1 of type String
and value name of type String
match expected type String
hello

4. 隐式类

在scala2.10后提供了隐式类,可以使用implicit声明类,隐式类的非常强大,同样可以扩展类的功能,比前面使用隐式转换丰富类库功能更加的方便,在集合中隐式类会发挥重要的作用。

隐式类使用有如下几个特点:

  1. 其所带的构造参数有且只能有一个
  2. 隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是顶级的(top-level objects),否则,就作用在整个程序中了,这显然是不可取的。
  3. 隐式类不能是case class(case class在后续介绍 样例类)
  4. 作用域内不能有与之相同名称的标识符

应用案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package implicitPack

object T5 {
def main(args: Array[String]): Unit = {
//DB1会对应生成隐式类
implicit class DB1(val m: MySQL1) {
def addSuffix(): String = {
m + " scala"
}
}
val mysql1 = new MySQL1
mysql1.sayOk()//输出:sayOK
println(mysql1.addSuffix())//输出:implicitPack.MySQL1@3c5a99da scala
}
}

class MySQL1 {
def sayOk(): Unit = {
println("sayOk")
}
}

该代码反编译为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//1
package implicitPack;

public final class T5$ {
public static T5$ MODULE$;

private static final T5$DB1$1 DB1$2(MySQL1 m) {
return new T5$DB1$1(m);
}

public void main(String[] args) {
MySQL1 mysql1 = new MySQL1();
mysql1.sayOk();
scala.Predef$.MODULE$.println(DB1$2(mysql1).addSuffix());
}

private T5$() {
MODULE$ = this;
}
}

//2
package implicitPack;

import scala.Predef$;
import scala.Predef$any2stringadd$;

public class T5$DB1$1 {
private final MySQL1 m;

public MySQL1 m() {
return this.m;
}

public T5$DB1$1(MySQL1 m) {}

public String addSuffix() {
return Predef$any2stringadd$.MODULE$.$plus$extension(Predef$.MODULE$.any2stringadd(m()), " scala");
}
}

//3
package implicitPack;

import scala.Predef$;
import scala.reflect.ScalaSignature;

@ScalaSignature(bytes = "\006\001]1Aa\001\003\001\017!)a\002\001C\001\037!)!\003\001C\001'\t1Q*_*R\031FR\021!B\001\rS6\004H.[2jiB\0137m[\002\001'\t\001\001\002\005\002\n\0315\t!BC\001\f\003\025\0318-\0317b\023\ti!B\001\004B]f\024VMZ\001\007y%t\027\016\036 \025\003A\001\"!\005\001\016\003\021\tQa]1z\037.$\022\001\006\t\003\023UI!A\006\006\003\tUs\027\016\036")
public class MySQL1 {
public void sayOk() {
Predef$.MODULE$.println("sayOk");
}
}

编译器优先级:传值 > 隐式值 > 默认值,但是不能有二义性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package implicitPack

object TT {

def main(args: Array[String]): Unit = {
implicit val name:String="aaa"
def hello(implicit content:String="jack"): Unit ={
println("hello:"+content)
}
def hello2(implicit content:Int=3): Unit ={
println("hello2:"+content)
}
hello//隐式值方式,输出hello:aaa
hello("bbb")//传值方式,输出hello:bbb
hello2//默认方式,输出hello:3

}
}

5. 隐式的转换时机

  1. 当方法中的参数的类型与目标类型不一致时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package implicitPack

object T6 {

def main(args: Array[String]): Unit = {
implicit def f1(d:Double):Int={
d.toInt
}
def test1(n1:Int){
println("OK")
}
test1(10.5)//隐式转换

}
}
  1. 当对象调用所在类中不存在的方法或成员时,编译器会自动将对象进行隐式转换(根据类型)

6. 隐式解析机制

即编译器是如何查找到缺失信息的,解析具有以下两种规则:

  1. 首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。(一般是这种情况)
  2. 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生模块,一个隐式实体的类型T它的查找范围如下(第二种情况范围广且复杂在使用时,应当尽量避免出现,了解一下就行):
    a) 如果T被定义为T with A with B with C,那么A,B,C都是T的部分,在T的隐式解析过程中,它们的伴生对象都会被搜索。
    b) 如果T是参数化类型,那么类型参数和与类型参数相关联的部分都算作T的部分,比如List[String]的隐式搜索会搜索List的伴生对象和String的伴生对象。
    c) 如果T是一个单例类型p.T,即T是属于某个p对象内,那么这个p对象也会被搜索。
    d) 如果T是个类型注入S#T,那么S和T都会被搜索。

7. 隐式转换的前提

在进行隐式转换时,需要遵守两个基本的前提:

  1. 不能存在二义性
  2. 隐式操作不能嵌套使用

如:隐式转换函数

1
2
3
4
5
6
7
def main(args: Array[String]): Unit = {
implicit def f1(d:Double):Int={
d.toInt
val num:Int=3.5// 隐式操作不能嵌套使用
}

}