导语:2017年Google IO大会宣布使用Kotlin做为Android的官方开发语言,相比较与典型的面相对象的JAVA语言,Kotlin做为一种新式的函数式编程语言,也有人称之为Android平台的Swift语言。java
先让咱们看下实现一样的功能,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的扩展函数与高阶函数,本文主要介绍一下扩展函数。app
Kotlin 可以扩展一个类的新功能而无需继承该类,或者对任意的类使用像“装饰者(Decorator)”这样的设计模式。这些都是经过叫作“扩展(extensions)”的特殊声明实现的。Kotlin扩展声明既支持扩展函数也支持扩展属性,本文主要讨论扩展函数,至于扩展属性实现的机制相似。jvm
扩展函数的声明很是简单,他的关键字是.,此外咱们须要一个“接受者类型(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声明一个普通函数并没有区别。ide
额外提一句,Kotlin的this语法
要比JAVA更灵活,这里扩展函数体里的this
表明的是接受者类型对象。函数式编程
若是咱们想要调用这个扩展函数,能够这样:函数
fun use(){ val list = mutableListOf(1,2,3) list.swap(1,2) }
扩展函数的调用看起来就像是原生方法同样天然,使用起来也很是顺手,可是这样的方法会不会带来性能方面的掣肘呢?有必要探究一下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.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(){ println("foo in Class E") } } class F{ fun foo(){ println("foo in Class F") } fun E.foo2(){ this.foo() this@F.foo() } } E().foo2()
这里使用了kotlin的this指定语法,关键字是@,后接指定的类型,上述例子的输出结果是
foo in Class E foo in Class F
通常来讲,咱们习惯将扩展函数直接定义在包内,例如:
package com.example.extension fun MutableList<Int>.swap(index1:Int,index2:Int) { val tmp = this[index1] this[index1] = this[index2] this[index2] = tmp }
这样,在同一个包内能够直接调用改扩展函数,若是咱们须要跨包调用扩展函数,咱们须要经过import来指明,以上述的例子为例,能够经过import com.example.extension.swap
来指定这个扩展函数,也能够经过import com.example.extension.*
表示引入该包内的全部扩展函数。得益于Android Studio具有的自动联想能力,一般不须要咱们主动输入import
指令。
有时候,咱们也会把扩展函数定义在类的内部,例如:
class G { fun Int.foo(){ println("foo in Class G") } }
这里的Int.foo()
是一个定义在类G内部的扩展函数,在这个扩展函数里,咱们直接使用Int
类型做为接受者类型,由于咱们将扩展函数定义在了类的内部,即便咱们设置访问权限为public
,它也只能在该类或者该类的子类中被访问,若是咱们设置访问权限为private,那么在子类中也不能访问这个扩展函数。
5.1 Utils工具类
在JAVA中,咱们习惯将工具类命名成*Utils
,例如FileUtils
,StringUtils
等等,著名的java.util.Collections
也是这么实现的。调用这些方法的时候,总以为这些类名碍手碍脚的,例如这样:
// Java Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list)); Collections.max(list));
经过静态引用,能让状况看起来好一点,例如这样:
// Java swap(list, binarySearch(list, max(otherList)), max(list));
可是这样既没有IDE的自动联想提示,方法调用的主体也显得不明确。若是能作成下面这样就行了:
// Java list.swap(list.binarySearch(otherList.max()), list.max());
可是list是JAVA默认的基础类,在JAVA语言里,若是不使用继承,确定是无法作到这样的,而在Kotlin中就能够借助扩展函数来实现啦。
5.2 Android View 胶水代码
回到最开始的例子,对于Android开发来讲,对findViewById()
这个方法必定不会陌生,为了获取一个View对象,咱们总得先调用findViewById()
而后再执行类型转换,这样无心义的胶水代码让Activity
或者Fragment
显得臃肿无比,例如:
// JAVA 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 fun <T : View> Activity.find(@IdRes id: Int): T { return findViewById(id) as T }
调用的时候,以下:
// Kotlin ... TextView label = find(R.id.label); Button btn = find(R.id.btn); ...
只是咱们仍是须要获取到label
,btn
,这样无心义的中间变量,若是在Int类上扩展,能够直接对R.id.*
操做,这样更直接,再结合高阶函数,函数定义以下:
//Kotlin fun Int.setText(str:String){ val label = find<TextView>(this).apply { text = str } } fun Int.onClick(click: ()->Unit){ val tmp = find<View>(this).apply { setOnClickListener{ click() } } }
咱们就能够这样调用:
//Kotlin R.id.label.setText("hello") R.id.label.onClick { Log.d("Glen","onClick TextView") } R.id.btn.onClick { Log.d("Glen","onClick Button") }
一般这些扩展函数能够放到基类中,根据扩展函数的做用域知识,咱们能够在全部子类中都调用到这些方法,因此kotlin的Activity
能够写成:
// Kotlin class MainKotlinActivity:KotlinBaseActivity() { 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") } } }
从原来JAVA冗余的20多行代码,精简到只须要3行代码,并且代码可读性更高,更加直观,这即是函数式编程语言Kotlin的强大威力。
问答
什么是Kotlin的“接收器”?
相关阅读
你为何须要 Kotlin
手Q Android线程死锁监控与自动化分析实践
为何说Kotlin的可读性比Java好?
此文已由做者受权腾讯云+社区发布,原文连接:https://cloud.tencent.com/developer/article/1146533?fromSource=waitui
欢迎你们前往腾讯云+社区或关注云加社区微信公众号(QcloudCommunity),第一时间获取更多海量技术实践干货哦~