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

**本文由腾讯Bugly发表在腾讯云+社区 **

先让我们看下实现同样的功能,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(){
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

4. 扩展函数的作用域

一般来说,我们习惯将扩展函数直接定义在包内,例如:

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. 扩展函数的实际应用

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),第一时间获取更多海量技术实践干货哦~

你还在把Java当成Android官方开发语言吗?Kotlin了解一下!的更多相关文章

  1. Google推Android新开发语言Sky:流畅度 秒iOS

    Dart初衷 作为当前市占率最高的智能手机操作系统,Android平台正在吸引着越来越多的开发者. 不过,对用户而言,Android的体验还不够完善,卡顿的情况时有发生.再深入点理解,许多应用的帧率达 ...

  2. 【Java】Android EditText开发的一个容易忽略的坑

    这几天接手做一个远程控制Android application,安卓前台的EditText用来输入ip地址.端口等信息,发现EditText的使用存在着巨坑一个! 我在网上找了半天,发现仅仅有人提出这 ...

  3. android官方开发教程解释(一)

    最近准备系统学一下android开发,这里不会照搬原文,只会针对教程中一些难以理解的部分进行解释,我只是个菜鸟. 在教程第一章——入门基础里面,讲解android主题的那个小节,大概会有以下的代码: ...

  4. 如何看待 Kotlin 成为 Android 官方支持开发语言?

    Google IO 2017宣布了 Kotlin 会成为 Android 官方开发语言.一时间朋友圈和Android圈被各种刷屏.当然我也顺势而为发布了一篇的文章<为什么我要改用Kotlin&g ...

  5. 如何看待 Kotlin 成为 Android 官方支持开发语言

    Google IO 2017宣布了 Kotlin 会成为 Android 官方开发语言.一时间朋友圈和Android圈被各种刷屏.当然我也顺势而为发布了一篇的文章<为什么我要改用Kotlin&g ...

  6. 弃 Java 而使用 Kotlin 的你后悔了吗?| kotlin将会是最好的开发语言

    自从 2011 年发布以来,Kotlin 凭借强大的功能在开发者中的欢迎程度与日俱增.且在一年前,Google 宣布 Kotlin 正式成为 Android 官方开发语言,由此引发了从 Java 迁移 ...

  7. 手机APP开发:学JAVA转安卓APP开发是不是很容易?

    成都亿合云商小编为您分享:Android开发是以Java语言为基础的,Android 虽然使用Java 语言作为开发工具,但是在实际开发中发现,还是与Java SDK 有一些不同的地方.Android ...

  8. android测试开发概念

    一:测试分类 1.分类概览 按测试阶段划分: 单元测试 集成测试 系统测试 验收测试 按是否覆盖源代码: 黑盒测试: 功能测试: 界面测试 逻辑测试 安装测试 应用性测试 兼容性测试 性能测试: 稳定 ...

  9. Android官方数据绑定框架DataBinding

    数据绑定框架给我们带来了更大的方便性,以前我们可能需要在Activity里写很多的findViewById,烦人的代码也增加了我们代码的耦合性,现在我们马上就可以抛弃那么多的findViewById. ...

随机推荐

  1. Microsoft.Office.Interop.Word.DocumentClass.SaveAs 命令失败

    asp.net 常用的生成word功能,代码也是网上常见的,自己本地反复测试过没问题.serves 2003下运行没问题,可是发布到2008上就出错.组件权限已配置,windows目录下temp权限已 ...

  2. android studio中使用recyclerview小白篇(二)

    前面一个说了怎么把这个包引用进来,这一节说怎么做一个简单的例子出来,我也是照着别人的例子写的,然后慢慢改就行了,做好的效果如下图 1.在我们的activity_main中把recyclerview填加 ...

  3. C#设计模式系列:适配器模式(Adapter Pattern)

    一.引言 在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用.但是新环境要求的接口是这些现存对象所不满足的.如何应对这种“迁移的变化”?如何既能利用现有对象的良好实现,同 ...

  4. asp.net core 外部认证多站点模式实现

    PS:之前因为需要扩展了微信和QQ的认证,使得网站是可以使用QQ和微信直接登录.github 传送门 .然后有小伙伴问,能否让这个配置信息(appid, appsecret)按需改变,而不是在 Con ...

  5. centos7下git的安装和配置

    git的安装: yum 源仓库里的 Git 版本更新不及时,最新版本的 Git 是 1.8.3.1,但是官方最新版本已经到了 2.9.2.想要安装最新版本的的 Git,只能下载源码进行安装. 1. 查 ...

  6. RadASM的主题更换!

    RadASM的代码编辑器默认背景色位黑色,我很不习惯,决定更换它,按照下面步骤,我把RadASM的代码编辑器默认背景色成功更换成了白色: 1, 2, 3, 4,

  7. 20165219 2017-2018-2《Java程序设计》课程总结

    20165219 2017-2018-2<Java程序设计>课程总结 一.每周作业链接汇总 20165219 我期望的师生关系 20165219学习基础与C语言基础调查 20165219 ...

  8. bzoj2115(线性基)

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2115 题意:求图中路径1~n上最大边权 xor 和 思路:参见 blog http://b ...

  9. docker镜像的创建

    获得更多资料欢迎进入我的网站或者 csdn或者博客园 昨天讲解了docker的安装与基本使用,今天给大家讲解下docker镜像的创建的方法,以及push到Docker Hub docker安装请点击右 ...

  10. 从pg_hba.conf文件谈谈postgresql的连接认证

    最近一直在弄postgresql的东西,搭建postgresql数据库集群环境什么的.操作数据库少不得要从远程主机访问数据库环境,例如数据库管理员的远程管理数据库,远程的客户存取数据库文件. 而在po ...