Kotlin扩展函数与实现机制探索-深蓝源码网


时间: 2020-09-03 00:08:26 人气: 2279 评论: 0

| 导语 2017年Google IO大会宣布使用Kotlin作为Android的官方开发语言,相比较与典型的面相对象的JAVA语言,Kotlin作为一种新式的函数式编程语言,也有人称之为Android平台的Swift语言。

先让我们看下实现同样的功能,Java和Kotiln的对比:

// JAVA,20多行代码,充斥着findViewById,类型转换,匿名内部类这样的无意义代码

public class MainJavaActivity extends Activity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {        
    super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView label = (TextView) findViewById(R.id.label);
        Button btn = (Button) findViewById(R.id.btn);

        label.setText("hello");
        label.setOnClickListener(new View.OnClickListener() {           
            @Override
            public void onClick(View v) {
                Log.d("Glen","onClick TextView");
            }
        });
        btn.setOnClickListener(new View.OnClickListener(){            
            @Override
            public void onClick(View v) {
                Log.d("Glen","onClick Button");
            }
        });
    }
}

再来看Kotlin

// Kotlin,没有了冗余的findViewById,我们可以直接对资源id进行操作,也不需要匿名内部类的声明,更关注函数的实现本身,抛弃了复杂的格式
class MainKotlinActivity:Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        R.id.label.setText("hello")
        R.id.label.onClick { Log.d("Glen","onClick TextView") }
        R.id.btn.onClick { Log.d("Glen","onClick Button") }
    }
}

实现这些需要借助Kotlin的扩展函数与高阶函数,本文主要介绍一下扩展函数。

1. Kotlin 扩展函数与扩展属性(Kotlin Extensions)

Kotlin 能够扩展一个类的新功能而无需继承该类,或者对任意的类使用像“装饰者(Decorator)”这样的设计模式。这些都是通过叫做“扩展(extensions)”的特殊声明实现的。Kotlin扩展声明既支持扩展函数也支持扩展属性,本文主要讨论扩展函数,至于扩展属性实现的机制类似。

扩展函数的声明非常简单,他的关键字是.,此外我们需要一个“接受者类型(recievier type)”来作为他的前缀。以类MutableList<Int>为例,现在为它扩展一个swap方法,如下:

fun MutableList<Int>.swap(index1:Int,index2:Int) {
    val tmp = this[index1]
    this[index1] = this[index2]
    this[index2] = tmp
}

MutableList<T>是kotlin提供的基础库collection中的List容器类,这里在声明里作为“接受者类型”,.作为声明关键字,swap是扩展函数名,其余和Kotlin声明一个普通函数并无区别。

额外提一句,Kotlin的this语法要比JAVA更灵活,这里扩展函数体里的this代表的是接受者类型对象。

如果我们想要调用这个扩展函数,可以这样:

fun use(){
    val list = mutableListOf(1,2,3)
    list.swap(1,2)
}

2. Kotlin扩展函数是怎么实现的

扩展函数的调用看起来就像是原生方法一样自然,使用起来也非常顺手,但是这样的方法会不会带来性能方面的掣肘呢?有必要探究一下Kotlin是如何实现扩展函数的,直接分析Kotlin源码难度还是挺大,还好Android Studio提供了一些工具,我们可以通过Kotlin ByteCode指令,查看Kotlin语言转换的字节码文件,仍以MutableList<Int>,swap为例,转换为字节码之后的文件如下:

// ================com/example/glensun/demo/extension/MutableListDemoKt.class =================
// class version 50.0 (50)
// access flags 0x31

public final class com/example/glensun/demo/extension/MutableListDemoKt {  

   // access flags 0x19
  // signature (Ljava/util/List<Ljava/lang/Integer;>;II)V
  // declaration: void swap(java.util.List<java.lang.Integer>, int, int)
  public final static swap(Ljava/util/List;II)V
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "$receiver"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 8 L1
    ALOAD 0
    ILOAD 1
    INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object;
    CHECKCAST java/lang/Number
    INVOKEVIRTUAL java/lang/Number.intValue ()I
    ISTORE 3
   L2
    LINENUMBER 9 L2
    ALOAD 0
    ILOAD 1
    ALOAD 0
    ILOAD 2
    INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object;
    INVOKEINTERFACE java/util/List.set (ILjava/lang/Object;)Ljava/lang/Object;
    POP
   L3
    LINENUMBER 10 L3
    ALOAD 0
    ILOAD 2
    ILOAD 3
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    INVOKEINTERFACE java/util/List.set (ILjava/lang/Object;)Ljava/lang/Object;
    POP
   L4
    LINENUMBER 11 L4
    RETURN
   L5
    LOCALVARIABLE tmp I L2 L5 3
    LOCALVARIABLE $receiver Ljava/util/List; L0 L5 0
    LOCALVARIABLE index1 I L0 L5 1
    LOCALVARIABLE index2 I L0 L5 2
    MAXSTACK = 4
    MAXLOCALS = 4

  @Lkotlin/Metadata;(mv={1, 1, 7}, bv={1, 0, 2}, k=2, d1={"\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0002\u0010!\n\u0002\u0010\u0008\n\u0002\u0008\u0003\u001a \u0010\u0000\u001a\u00020\u0001*\u0008\u0012\u0004\u0012\u00020\u00030\u00022\u0006\u0010\u0004\u001a\u00020\u00032\u0006\u0010\u0005\u001a\u00020\u0003\u00a8\u0006\u0006"}, d2={"swap", "", "", "", "index1", "index2", "production sources for module app"})  
// compiled from: MutableListDemo.kt

}
// ================META-INF/production sources for module app.kotlin_module =================

这里的字节码已经相当直观,更令人惊喜的是Android Studio还具备将字节码转为JAVA文件的能力,点击上面的Decompile按钮,可以得到如下JAVA代码:

import java.util.List;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 7},
   bv = {1, 0, 2},
   k = 2,
   d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0002\u0010!\n\u0002\u0010\b\n\u0002\b\u0003\u001a \u0010\u0000\u001a\u00020\u0001*\b\u0012\u0004\u0012\u00020\u00030\u00022\u0006\u0010\u0004\u001a\u00020\u00032\u0006\u0010\u0005\u001a\u00020\u0003¨\u0006\u0006"},
   d2 = {"swap", "", "", "", "index1", "index2", "production sources for module app"}
)

public final class MutableListDemoKt {   
    public static final void swap(@NotNull List $receiver, int index1, int index2) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");      
    int tmp = ((Number)$receiver.get(index1)).intValue();
      $receiver.set(index1, $receiver.get(index2));
      $receiver.set(index2, Integer.valueOf(tmp));
   }
}

从得到的JAVA文件分析,扩展函数的实现非常简单,它没有修改接受者类型的成员,仅仅是通过静态方法来实现的。这样,我们虽然不必担心扩展函数会带来额外的性能消耗,但是它也不会带来性能上的优化。

3.更复杂的情况

下面来讨论一些更特殊的情况。

3.1 当发生继承时,扩展函数由于本质上是静态方法,它会严格按照参数类型去执行调用,而不会去优先执行或者主动执行父类的方法,如下的例子所示:

open class A

class B:A()

fun A.foo() = "a"

fun B.foo() = "b"

fun printFoo(a:A){
   println(a.foo())
}

println(B())

上述例子的输出结果是a,因为扩展函数的入参类型是A,他将会严格按照入参类型执行函数调用。

3.2 如果扩展函数和现有的类成员发生冲突,kotlin将会默认使用类成员,这一步选择是在编译期处理的,生成的字节码是将会是调用类成员的方法,如下例子:

class C{    
    fun foo() {println("Member")}
}

fun C.foo() {println("Extension")}

println(C().foo())

上述的例子将会输出Member。Kotlin不允许扩展一个已有的成员,原因也很好理解,我们不希望扩展函数成为调用三方sdk的漏洞,不过如果你试图使用重载的方式创建扩展函数,这样是可行的。

3.3 Kotlin严格区分了可能为空和不为空的入参类型,同样也应用在扩展函数的中,为了声明一个可能为空的接受者类型,可以参考如下例子:

fun <T> MutableList<T>?.swap(index1:Int,index2:Int){
    if(this == null){
        println(null)
        return
    } 

    val tmp = this[index1]    
    this[index1] = this[index2]    
    this[index2] = tmp
}

3.4 我们有时候还希望能够添加类似JAVA的“静态函数”的扩展函数,这时需要借助“伴随对象(Companion Object)”来实现,如下这个例子:

class D{
    companion object{
        val m = 1
    }
}

fun D.Companion.foo(){
    println("$m in extension")
}

D.foo()

上面的例子会输出1 in extension,注意这里调用foo这个扩展函数时,并不需要类D的实例,类似于JAVA的静态方法。

3.5 如果留意前面的例子,我们会发现kotlin的this语法和JAVA不同,使用范围更灵活,仅以扩展函数为例,当在扩展函数里调用this时,指代的是接受者类型的实例,那么如果这个扩展函数声明在一个类内部,我们如何通过this获取到类的实例呢?可以参考下面的例子:

class E{    
    fun foo()
						 技术沙龙 教程文章 热点综合					

评论