目录
前言
当"隐式"不生效时
定义普通变量进行编译
单独定义隐式值,没有隐式参数与其对应时
隐式方法
类型转换
功能扩展
隐式值和隐式参数
隐式类
武魂融合技
隐式值是匿名内部类
implicitly
前言
scala 同 java 一样,运行在 JVM 上.其语法的灵活和易用可以理解为都是在 java 语言的基础上封装了丰富的语法糖.那scala 不同的语言特性"翻译"为 java 又是什么样子,Scala 剖析系列将会从零开始进行探索.
本文主要讲述了 scala 中隐式转换在反编译 Java 结果中的体现,目的是从 Java 的角度充分理解隐式转换.文章适合有 scala 基础清楚 implicit 相关语法和应用场景的同学阅读.
当"隐式"不生效时隐式值或者隐式方法都是在被需要的时候才会生效.比如我们定义了一个单独的隐式值,而整个代码的作用域内没有需要隐式值填充的方法,则该隐式值应该是不生效的.下面我们举例说明:
定义普通变量进行编译scala 代码如下
object ImplicitTest{
val i : Int = 1
}
反编译后的 Java 代码如下
public final class ImplicitTest$ {
public static ImplicitTest$ MODULE$;
private final int i;
public int i() {
return this.i;
}
private ImplicitTest$() {
MODULE$ = this;
this.i = 1;
}
}
为什么编译结果是这个样子在上一章 val,var和 def 里有详细讲述,此处不再赘述.
单独定义隐式值,没有隐式参数与其对应时scala代码:
object ImplicitTest{
implicit val i : Int = 1
}
反编译后的 Java 代码:
public final class ImplicitTest$ {
public static ImplicitTest$ MODULE$;
private final int i;
public int i() {
return this.i;
}
private ImplicitTest$() {
MODULE$ = this;
this.i = 1;
}
}
可以看到隐式值在没有触发其"隐式"特性的时候,字节码层面同普通的变量定义并没有区别.隐式方法也是同样的道理,不再举例说明.
隐式方法 类型转换隐式方法的作用是在传入方法的参数类型不是其所期望的类型时,会自动寻找是否存在对应的隐式转换方法.该功能在字节码的体现上是怎样的呢?
下边的一段代码定义了一个 String2Int 的隐式函数和一个需要 Int 类型的方法,此时在 main 函数中给 needInt 方法传入一个 String 类型的参数,会触发隐式转换.
object ImplicitTest{
implicit def implicitFunc(s: String) = {s.toInt}
def main(args: Array[String]): Unit = {
val s : String = "1"
needInt(s) // int
}
def needInt(i : Int) = {println(i.getClass.getSimpleName)}
}
反编译的 Java 代码 ImplicitTest$.class main 方法:
public void main(String[] args) {
String s = "1";
needInt(implicitFunc(s));
}
可以看到 needInt 在传入 String 变量 s 时,会自动调用隐式方法.而隐式方法 implicitFunc 本身在反编译的结果中并没有其他特殊的 *** 作.只是一个简单的静态方法. ImplicitTest.class 代码如下
public final class ImplicitTest {
public static void needInt(int paramInt) {
ImplicitTest$.MODULE$.needInt(paramInt);
}
public static void main(String[] paramArrayOfString) {
ImplicitTest$.MODULE$.main(paramArrayOfString);
}
public static int implicitFunc(String paramString) {
return ImplicitTest$.MODULE$.implicitFunc(paramString);
}
}
所以我们可以理解为 implicit 只被 scala 的编译器识别,JVM 在指令集中(粗略的翻了一下)没有其对应的 *** 作.
功能扩展隐式方法的另一个功能是对现有类进行功能扩展.语法和概念此处不详细描述,我们主要看scala 代码案例和它反编译后的结果
scala 代码
object ImplicitTest{
implicit def DB2Dog(db: DB) = {new Dog}
def main(args: Array[String]): Unit = {
val db: DB = new DB
db.sleep //sleep
}
}
class DB{
def read = {println("read")}
}
class Dog{
def sleep = {println("sleep")}
}
定义了两个简单的类 DB 和 Dog,通过隐式转换的方式给 DB 类扩展了 Dog 的 sleep 方法.那在字节码反编译为 Java 时又是如何实现的呢?Java 代码如下:
public Dog DB2Dog(DB db) {
return new Dog();
}
public void main(String[] args) {
DB db = new DB();
DB2Dog(db).sleep(); // 重点
}
可以看到,还是同样简单的对代码做了自动化的嵌套. DB2Dog(db)返回的就是一个 Dog 对象,而 Dog 对象本身具有 sleep 方法所以能够完成调用.
隐式值和隐式参数前边讲到,单独定义隐式值而 没有方法需要 该隐式值类型的隐式参数时(有点绕),隐式值是不生效的.所以隐式值一般都是同隐式参数配合使用.因为隐式值的自动装配特性,项目中大多都是柯里化函数搭配隐式参数,看个代码样例:
object ImplicitTest{
def main(args: Array[String]): Unit = {
implicit val s : String = "String"
val i: Int = 1
needIntAndStr(i)// 1 String
}
def needIntAndStr(i : Int)(implicit s: String) = {
print(i + "\t" + s)
}
}
needIntAndStr需要两个参数,只显示的传递一个i,隐式值 s 会自动装配.方法输出结果为 "1 String".反编译后的 Java 代码如下:
ImplicitTest$.class的 main 方法中定义的 s 直接去掉了 implicit 关键字
public void main(String[] args) {
String s = "String";
int i = 1;
needIntAndStr(i, s);
}
needIntAndStr方法也直接被编译为了需要两个参数
public void needIntAndStr(int i, String s) {
scala.Predef$.MODULE$.print((new StringBuilder(1)).append(i).append("\t").append(s).toString());
}
代码里的一个小细节: String 在执行加运算时,实际底层会创建一个 StringBuilder 进行不断的 append.这一点不光在 scala 的反编译场景下,在 Java 中也是通用的.使用StringBuilder会大大提高代码运行效率.
隐式类隐式类实际上是隐式方法的另一种方便形式.当我们需要对一个类行进多项功能的扩展,而这些功能又分布在不同的功能类中时,就需要定义多个隐式方法,而隐式类可以将所有方法写在一个类中并省略 implict 关键字.样例如下:
object ImplicitTest{
def main(args: Array[String]): Unit = {
1.add(1)
1.inc()
}
implicit class IntPlus(i : Int) {
def add(o : Int) = {i + o}
def inc() = {i+=1}
}
}
而隐式类进行多种功能扩展和多种类型转换时,反编译的结果同隐式方法的情况相同,就不再举例.
武魂融合技 上下文界定Scala 的上下文界定中,要求必须有期界定类型XXX[DB]的隐式值.scala 代码如下
上图的代码中,needDB 使用了上下文界定(T: DB),所以在调用 needDB 方法的时候必须存在DB[T], 所以上边代码中增加一个对应的隐式值就可以正常运行了.
object ImplicitTest{
def main(args: Array[String]): Unit = {
implicit val db: DB[Int] = new DB[Int] // 隐式值
needDB[Int]
}
//上下文界定
def needDB[T: DB] = {println("needDB exec")}
}
class DB[A]{
def read(a: A) : String = "String"
}
反编译为 Java 代码:
public void main(String[] args) {
DB> db = new DB();
needDB(db);
}
public void needDB(DB evidence$1) {
scala.Predef$.MODULE$.println("needDB exec");
}
视界(view bounds)是对隐式转换在泛型上的简单应用,就是当泛型设置为 class A[B <% C]时,C 应该是 B 的子类,如果找不到子类,就去隐式转换里找有没有可以把其他对象转换为 C 的方法,如果有,则调用隐式转换.比较简单,不展开讲啦.
隐式值是匿名内部类当隐式值是一个匿名内部类时,好像显得稍稍复杂一点,但其实同隐式值和隐式参数小节讲的没有区别.
scala 代码
object ImplicitTest{
def main(args: Array[String]): Unit = {
implicit val db: DB[Int] = new DB[Int]{
def read(i: Int) = i.toString
}
needDB
}
def needDB(implicit db: DB[Int]) = {db.read(1)}
}
trait DB[A]{
def read(a: A) : String
}
不管DB[Int]是匿名内部类还是直接创建对象,翻译为 Java 代码,main 方法中needDB 方法都会被编译为需要一个 db 参数. ImplicitTest$$anon$1就是实现于DB[Int]的匿名类的名字.Java 反编译代码如下:
public void main(String[] args) {
DB
结合 1.2 两点,可以发现上下文限定和隐式参数的形式是十分类似的,只是上下文限定可以动态指定要哪个类型的隐式参数.
一般上下文限定都会跟 implicitly 方法结合使用.
implicitlyimplicitly是 scala 的 Predef 包里的方法.方法签名如下
def implicitly[T](implicit e: T) = e
定义了一个T 类型的隐式参数 e,方法返回值就是这个 e. 所以这个方法的功能就是寻找作用域内对应的隐式参数.如果我们定义 val db = implicitly[DB[Int]], 变量 db 的结果就是一个 DB[Int] 类的对象.
综合以上三点,我们可以写出如下的代码
object ImplicitTest{
def main(args: Array[String]): Unit = {
implicit val db: DB[Int] = new DB[Int]
needDB[Int](1)
}
def needDB[T: DB](i: T) = {
val db: DB[T] = implicitly[DB[T]]
db.read[T](i)
}
}
class DB[A]{
def read[A](a: A) : String = a.toString
}
needDB 中没有使用隐式参数和显式参数,通过 上线文界定+ implicitly 方法就可以得到想要的 db 对象进行功能调用.该功能的应用场景主要是在函数 needDB 已经是柯里化且传递了一个 implicit 参数的情况下.因为函数柯里化只能有一个隐式参数.
以上就是 scala 中跟隐式转换相关的内容.感谢您的耐心阅读.
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)