文章目录

  • ​​隐式转换​​
  • ​​隐式转换函数​​
  • ​​隐式转换丰富类功能​​
  • ​​隐式值​​
  • ​​隐式类​​
  • ​​隐式转换的时机​​
  • ​​隐式解析机制​​
  • ​​隐式转换的前提​​

隐式转换

隐式转换就是自动类型转换。 如 var dbl:Double = 12 int 自动转换double, 但是 val num:Int =3.5 确会报错。我们可以自己通过定义隐式转换来实现 double自动转换Int

隐式转换函数

隐式转换函数是以implicit 关键字声明的带有单个参数的函数,这种函数会自动应用将值从一种类型转换为另一种类型
注意事项:
1)隐式转换的函数名可以是任意的,饮食转换与函数名无关,只和函数签名(参数类型和返回值类型)有关
2)隐式函数可以有多个,但是要保证在当前环境下只有一个隐式函数可以被识别,如果有多个函数都可以使用 那编译器就不知道要用哪个了
示例

def test: Unit ={
var dbl:Double = 12 //可以自动实现类型转换
implicit def f1(d:Double): Int ={//定义一个隐式转换函数 将Double类型转换为 Int 类型
return d.intValue()
}
implicit def f2(d:Float): Int ={//定义一个隐式转换函数 将float类型转换为 Int 类型
return d.intValue()
}
// implicit def f3(d:Float): Int ={//定义一个隐式转换函数 将float类型转换为 Int 类型
// return d.intValue() //因为f3 和 f2 签名相同 两个方法冲突 必须删除一个 否则 float隐式转换int时编译器不知道用哪个
// }
val num:Int =3.5//定义隐式转换函数后 这种写法不再报错
var num2:Int =2.1f
}

反编译底层实现

public final class class09$
{
public static MODULE$;

static
{
new ();
}

public void test()
{
double dbl = 12.0D;

int num = f1$1(3.5D);
int num2 = f2$1(2.1F); }

public void main(String[] args) {
test();
}

private static final int f1$1(double d)
{
return Predef..MODULE$.double2Double(d).intValue(); }

private static final int f2$1(float d) {
return Predef..MODULE$.float2Float(d).intValue();
}

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

隐式转换丰富类功能

隐式转换可以丰富类功能
示例

class MySQL{//类 Mysq可以执行insert 操作
def insert(): Unit ={
println("insert")
}
}

class DB{//类 DB 可以执行delete 操作
def delete(): Unit ={
println("delete ~~~");
}
}
def test: Unit ={
//声明一个隐式函数,当在函数作用范围内创建一个 MySQL 并且实用 DB类中的方法时,可以自动转换为DB类
implicit def addDelete(mysql:MySQL ): DB ={
return new DB
}
var mysql = new MySQL
mysql.insert()
//因为有了上述隐式转换函数 mysql可以执行 DB类中的操作
//编译器在编译时会调用隐式函数 addDelete 并得到一个DB对象 然后执行 db对象的 delete方法
mysql.delete()
}

反编译

public void test()
{
class09.MySQL mysql = new class09.MySQL();
mysql.insert();

addDelete$1(mysql).delete(); }

隐式值

隐式值也叫做隐式变量,将某个形参变量标记为implicit后,如果调用方法时为传入参数,那么编译器会
搜索作用域内对应的隐式值作为缺省参数
注意事项:
1)在程序当中 调用一个方法时,同时有隐式值、默认值、和传入值 编译器的采用的优先级为 传值>隐式值>默认值
2)隐式值在匹配时,不能有二义性
3)如果方法调用时 没有传值 同时也没有 隐式值和默认值 那么 程序会报错
示例:

def test: Unit ={
implicit val name:String ="Scala";
//声明两个 String 类型的隐式值 后 程序 在匹配隐式值时 不知道选用哪个 因此会报错
//即 隐式值不能有二义性
//implicit val msg:String ="Hello";
def hello(implicit param:String): Unit ={
println("hi~"+param)
}
def hello2(implicit param:String="默认参数值"): Unit ={
println("hi~"+param)
}
hello //hi~Scala 法参数只有隐式值 因此 输出 隐式值
hello2//hi~Scala 法参数隐式值和默认值同时存在 使用隐式值
hello("传入值")//hi~传入值 方法参数有传入值时 使用传入值
//实时上 使用隐式值 发生在编译阶段 而使用 默认值 发生在 执行阶段,所以 隐式值得优先级比默认值高
编译后的test方法如下:
public void test()
{
String name = "Scala";

hello$1(name);
hello2$1(name);
hello$1("传入值"); }
}

隐式类

scala2.10之后提供了隐式类,可以使用implicit声明类,隐式类非常强大,同样可以扩展类的功能
比之前使用隐式转换丰富类功能更加方便,在计划中隐式类会发挥很大的作用
隐式类有以下特点
1)它构造器的参数有且只有一个
2)隐式类必须定义在类、伴生对象或包对象中,不能直接定义在包下(因为这样不明确作用范围)
3)隐式类不能是case class
4)作用域内不能有与之相同名称的标识符
示例:

class Mysql{
def sayHi(): Unit ={
println("hi~~~")
}
}

def test: Unit ={
/*
声明 DB为隐式类 当在该类的作用域范围内创建 Mysql对象时 该隐式类就会自动生效
(在调用DB1类的方法时自动根据实例构造DB1实例)
即 同上述隐式函数转换函数示例 ,
*/
implicit class DB(val m:Mysql){
def showInfo(): Unit ={
println(m.toString +"ddd")
}

}
var mysql=new Mysql
mysql.sayHi()
mysql.showInfo()//可以调用 DB 类中的方法
}

反编译 DB类如下 编译器生成的DB类 拥有一个Mysql类型的属性

public class class09$DB$1
{
private final class09.Mysql m;

public class09.Mysql m()
{
return this.m; }

public void showInfo() { Predef..MODULE$.println(3 + m().toString() + "ddd");
}
}

test 方法反编译如下,在调用DB的方法时 根据mysql实例创建一个 DB实例 然后执行方法

public final class class09$
{
public static MODULE$;

static
{
new ();
}

public void test()
{
class09.Mysql mysql = new class09.Mysql();
mysql.sayHi();
DB$2(mysql).showInfo(); }

public void main(String[] args) {
test();
}

private static final class09.DB.1 DB$2(class09.Mysql m)
{
return new class09.DB.1(m);
}

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

隐式转换的时机

1)当方法中的参数类型与目标类型不一致时,或者为变量赋值本身类型和目标类型不一直是 val a:Int=3.4
2) 当对象调用本类中不存在的方法或成员时,编译器会自动将对象进行隐式转换

隐式解析机制

编译器是如何查找到缺失信息并完成对应的隐式转换的?
1)首先编译器会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)
大部分都是这种情况
2)如果第一条规则查找失败,编译器会继续在隐式参数类型的作用域李查找。
类型的作用域指与该类型相关联的全部伴生模块。一个隐式实体的类型T查找范围如下
(这种情况范围广且复杂,实际开发中尽量避免使用)
a)如果T被定义为 T with A with B with C 那么 ABC 都是T 的部分,在T的隐式解析过程中他们的伴生对象都会被搜索
b)如果T是参数化类型,那么类型参数和与类型参数相关联的部分都算作T的部分,如
List[String]的隐式搜做会搜索List 和 String 的的伴生对象
c)如果T是一个单例类型, p.T,即T属于某个对象p,那么这个p对象也会被搜索
d)如果T是个类型注入 S#T,那么S和T 都会被搜索

隐式转换的前提

隐式转换时需要遵循两个基本前提
1)不能有二义性
2)隐式操作不能嵌套使用