scala 中的Map 初始化过程详解及隐式类型转换
在Scala 中, 可以这样初始化⼀个Map 对象:
这种创建Map 对象的⽅式, 给⼈⼀种优雅的感觉, 不得不佩服Scala 语⾔作者的想象⼒。 但是这种初始化的⽅式是如何实现的呢? ->是⼀个操作符吗? 还是⼀个⽅法? 如果是⼀个⽅法的话, String 对象上并没有这个⽅法, Object 对象上也没有这个⽅法, 那么字符串"US"是如何调⽤这个->⽅法的呢?带着这些问题, 我们写⼀个实例验证⼀下这种初始化是如何实现的。 ⽰例代码如下:
⼊⼝函数中只有⼀句代码, 这句代码以上述的⽅式创建⼀个Map 对象。在之前的博客中, 我们讲述过, 以object 关键字修饰的是单例对象, 这个单例对象编译成class ⽂件之后, 会有⼀个虚构类。 虚构类的名字为Main$.class 。 虚构类中有⼀个同名的成员⽅法main 。 Scala ⼊⼝函数的主要逻辑都在这个main ⽅法中。 关于单例对象的实现⽅式, 前⾯有⼏篇⽂章已经介绍过了, 这⾥不再赘述。 不清楚的读者可以参考前⾯的⼏篇博客:
菊花台吉他谱
蒸压灰砂砖我们知道, 创建map 对象的逻辑被编译在了Main$.class 的main 实例⽅法中。 下⾯我们反编译Main$.class , 看看到底是如何实现的。 下⾯给出Main$.class 中的main
⽅法反编译之后的字节码:
在Scala源码中,⼀句创建Map对象的代码竟然对应class⽂件中的29条字节码。这真实太神奇了,编译器给我们做了⼤量的⼯作,简化了我们的编码任务,但是提⾼了学习门槛,我们必须明⽩编译器额外为我们做了哪些⼯作,才能对Scala理解的⽐较深⼊。就像《Scala编程》⼀书的作者再书中说的那样:⼀边情况下你不必知道编译器做了什么,但是有时候掀开盖⼦看看下⾯有什么,能加深我们的理解(⼤概意思是这样,原话不记得了)。
下⾯我们就分析main⽅法中的字节码,看看到底是怎样创建Map对象的。
前两条字节码指令(索引为0和3)的意思是调⽤Predef$中的Map⽅法,该⽅法的返回值为scala/collection/immutable/Map,也就是说这个⽅法会创建⼀个Map对象。这⾥要说⼀句, Predef也是⼀个单例对象,所以编译之后肯定有⼀个虚构类Predef$ 。
索引为9和10 的两条字节码指令的意思是创建⼀个长度为2的,类型为scala/Tuple2的数组。
索引为21的ldc指令,访问常量池中的字符串“US” ,根据这个常量池字符串,创建字符串对象。渔米之乡
索引为23的invokevirtual指令调⽤Predef$中的any2ArrowAssoc ⽅法,这个⽅法的参数是java/lang/Object,返回值也是⼀个
java/lang/Object 。看到这⾥我们就感到奇怪了,为什么会调⽤这个⽅法呢?下⾯我们查看Predef的源
码,看看这个⽅法是如何实现的。相关源码如下:
肝火旺吃什么调理可以看出,这个any2ArrowAssoc ⽅法将传⼊的对象x包装成⼀个ArrowAssoc对象,这个对象是Predef的内部类,从上⾯的代码中可以看到,这个类中有⼀个叫做 ->的⽅法。这个⽅法根据传⼊的参数创建⼀个⼆元元组Tuple2 。
所以第23⾏的字节码指令的意义是:将字符串对象“US”装换成⼀个ArrowAssoc对象。
索引为26的ldc指令创建⼀个字符串对象“Washington” 。
索引为28的invokevirtual指令调⽤上⾯创建的ArrowAssoc对象的$minus$greater$extension⽅法。但
是我们在源码中并没有看到这个⽅法,但是从名字上可以猜测出, minus代表减号- , greater代表⼤于号> ,所以加起来就是->⽅法,所以猜测这⾥就是调⽤的ArrowAssoc中的->⽅法。这个⽅法的调⽤者是由“US”包装成的ArrowAssoc对象(索引为23的指令创建的),参数是索引为26的指令创建的字符串对
婚检检查项目象“Washington” 。所以到此为⽌,根据“US”和“Washington”创建了⼀个⼆元元组Tuple2 对象。桂圆肉功效
索引为31的aastore指令将上⾯创建的Tuple2 对象放⼊索引为10的字节码指令创建的Tuple2 数组中。
从索引32到索引50的字节码指令重复13到31的字节码指令,根据“France”和“Paris”创建⼀个Tuple2对象,并放⼊之前创建的Tuple2 数组中。
索引为54的invokevirtual指令调⽤Predef$中的wrapRefArray⽅法,将上⾯创建的Tuple2 数组对象包转成⼀个
scala/collection/mutable/WrappedArray对象。
索引为57的invokevirtual指令调⽤上⾯创建的scala/collection/immutable/Map对象(由索引为3的字节码指令创建)的apply⽅法,将上⾯的⼆元组Tuple2数组,存放到这个scala/collection/immutable/Map对象中,就完成了Map中数据的存储。这个apply⽅法定义在
scala/collection/immutable/Map的⽗类GenMapFactory中,定义如下:
到此为⽌, Map对象就创建完了,并且也把数据存到了Map对象中。
索引为63的astore_2指令将上⾯创建的Map对象保存到局部变量表中。
这个过程⽤Java表⽰的话,是这样的(只是为了说明原理,并不符合Java语法):
淘汰羊由此可见, Scalac编译器为我们做了⼤量⼯作。其中有⼀个地⽅要重点强调。那就是默认将字符串对象转成ArrowAssoc对象,并调⽤->⽅法。这是Scala中为了简化语法⽽引⼊的⼀个特性,叫做隐式类型转换。
蔓越莓干的功效