目录

什么是Kotlin?

去年,甲骨文再次对谷歌所谓的安卓侵权使用Java提起诉讼,要求后者赔偿高达90亿美元。随后便传出谷歌因此计划将主力语言切换到苹果主导的Swift,不过这事后来没了跟进。

但谷歌在2017年5月的I/O大会上宣布了新决定:Kotlin语言正式成为安卓开发的一级编程语言。资料显示,Kotlin由JetBrains公司开发,于2010年首次推出,次年开源。它与Java 100%互通,并具备诸多Java尚不支持的新特性,Android Studio(3.0)已提供支持。

为什么使用Kotlin

Kotlin 来源于一个岛屿的名字,全称是 Kotlin Island,是英语「科特林岛」之意。科特林岛位于俄罗斯的圣彼得堡以西约30公里处,形状狭长,东西长度约14公里,南北宽度约2公里,面积有16平方公里,扼守俄国进入芬兰湾的水道。

为什么使用Kotlin

说到Kotlin肯定就要提到Java,Java 语言的名字是来自于一个岛,那个岛就是印度尼西亚的爪哇(Java)岛,因盛产咖啡而闻名。所以 Kotlin 也得选一个岛作为名字。

优势

Kotlin 从不少语言获得灵感,比如 Java、Scala、Groovy、C#、Gosu,可以说是博采众长。既具备了快速开发的能力,编译运行也快,实现相同功能的代码长度可以比 Java 少很多。

  • 多平台开发的可能 :基于 JVM 的开发,Android 开发,Web 开发,Native(原生)开发。其中 Web 开发可以结合 Spring 框架,而且 Kotlin 也可以编译生成 JavaSript 模块,可以在一些 JavaScript 的虚拟机上编译。Native 开发就更牛了,目前 Kotlin 官方在 Github 开源了 Native 开发的源码 https://github.com/JetBrains/kotlin-native,基于 LLVM(Low Level Virtual Machine 的缩写,表示「底层虚拟机」。LLVM 是一种编译器基础设施,以 C++ 写成。它是为了任意一种编程语言而写成的程序,利用虚拟技术创造出编译时期、链接时期、运行时期以及闲置时期的最优化)的后端,方便为各个平台编写原生应用,比如为 Mac OS,iOS,Linux,嵌入式系统,等等。

  • 开源:闭源项目总归比较有限。众人拾柴火焰高,代码开源可以更快速地发现 Bug,有了全世界各地程序员的贡献,Kotlin 的优秀代码和库会越来越多。Linux 系统就是开源的很好例子。

  • 和 Java 100% 兼容 :Kotlin 调用 Java 已有的代码或库没有问题。在一个项目中也可以同时用 Java 和 Kotlin 来编写代码。Android Studio 和 IntelliJ IDEA 都可以实现一键转换 Java 代码到 Kotlin。官方也有专页介绍:https://www.jetbrains.com/help/idea/2017.1/mixing-java-and-kotlin-in-one-project.html

  • 安全 :大家聊得最多的肯定是可以轻松防止在 Java 中很常见的 NullPointerException(空指针异常)问题咯。做 Android 开发的一定深有体会,一般 app 奔溃,基本都是因为 NullPointerException,很多时候规避机制就是加一个 if 语句的判断,很累赘。

  • 容易学习 :Kotlin 语法很简单,和主流语言类似,语法高效,入门非常容易。好比当初苹果发布 Swift 也是因为 Objective-C 的语法奇怪,学习曲线比较陡峭。

  • Lambda 表达式。

  • 变量类型推断。

  • when 语句块 :告别繁琐的 switch 和 if... else if... else 语句块。

  • 非常方便的运算符重载。

  • 不再需要手动添加 get 和 set 方法对了,直接对类的私有变量赋值和取值。

  • Anko 这样的 Kotlin 的官方库可以使 Android 应用开发更快捷。

  • 函数/方法 的关键字是 fun,而不是 function。真的很简洁也很有趣(fun 是英语「有趣」的意思)。

  • Var 和 Val 关键字 :和 fun 关键字类似,也很简洁。Var 是 Variable(英语「可变的」之意)的缩写,表示「可变的」变量。Val 大概是 Value (英语「值」之意)的缩写,表示只能赋值,而不能改写其值,是表示「只读的」变量,有点类似 C 语言中的 const 变量。这两个关键字的起名估计是参考了 Scala 语言。

  • 类的方法扩展很方便。

  • 可以创建自定义的 DSL(领域特定语言)。

  • 优秀工具的支持 :JetBrains 公司开发了那么多优秀的工具,Kotlin 可以完全享用。

  • Coroutine :协程。

特性

数据类定义

public class Account {
    private long id;
    private String name;
    private String phoneNumber;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber= phoneNumber;
    }

    @Override public String toString() {
        return "Artist{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", phoneNumber='" + phoneNumber+ '\'''}';
    }
}

java一般这样,属性+设置+获取+toString(),一个数据类的基本功能就有了,三个属性的类写了三十几行代码。即使使用lombok插件,也要多写上注解,而Kotlin就简单了。

data class Account(
        var id: Long,
        var name: String,
        var phoneNumber: String)

上面类的定义,Kotlin声明属性是默认访问修饰符,而Java是private,如果kotlin也是private,那同样得定义一系列的set()/get(),不然在类外面无法通过对象获取属性。

除了代码量上的不同,还得关注data关键字和类名后面紧跟的属性列表。

先看参数列表,这里其实对应的是Java中的有参构造函数。如果Kotlin定义类时有参数列表,那么新建对象时必须传入形参值,不像Java有两种方式(1 先建一个无参对象,再通过set()来给属性赋值;2 直接通过参数列表创建对象)。但是使用过之后就会发现,Kotlin这种做法更简洁,且不会出现新建了对象而属性没有赋值的情况,也就更安全了。当然,Kotlin还是可以再定义其他的构造、初始化及一般功能性的方法。

创建对象

Java:

Account account = new Account(1, "Flicker", "13888888888");

Kotlin:

var account = Account(1, "Flicker", "13888888888")

Kotlin对象创建不用加new关键字,而且语句后面不用加分号";"(即使加上也会被忽略)。写多了Kotlin再写Java,感觉还挺别扭的。

toString()

而类名前的data关键字,是显示声明该类是作为数据类使用,通过toString()打印的结果可以看出区别,打印语句:

println("account.toString(): " + account.toString())

不加data结果:

account.toString(): Account@61ael9ba

加上data结果:

account.toString(): Account(id=1, name=Flicker, phoneNumber=13888888888)

可以看到toString()是Kotlin自动生成的,如果类声明不加data,只会打印出一串数字(应该是类的内存地址),而不是当前对象的属性信息。

变量空安全

不管是C++的指针,还是Java的引用,因指向对象为null导致的问题一直困扰着我们。Kotlin提供了一种安全机制,尽量减少变量在使用前是null的情况。

Java:

String str;
if (str != null) {
    //do something
}

对于Java代码,编译器不会强制每次使用引用变量之前进行null判断,即异常往往会在运行时报出,但这正是危险所在。

Kotlin:

var str1: String = null  //编译错误,kotlin规定如果显式指明了str1的类型,这里是String,声明时必须同时指定是否允许为空值(null),不加问号"?"表示不允许为null;
var str2: String? = null  //编译通过,作第一行代码的另一种情况,加了问号,并赋值为null;
var str3 = "testNull"  //编译通过,隐式赋值为"testNull",Kotlin会自动推断出str3类型为String,之后便不可再更改了,即不可再赋值为1这种整形数据;
var str4 = null  //编译通过,隐式赋值为null,那么str4就一直为null了;

var str5: String  
str5 = "testNull"  //编译通过,前者只是指定类型,没有赋值;后者赋予str5 String类型值"testNull"同样不能赋值为其他类型值;

var str6  //编译错误,既没有指定类型,也没有隐式地进行初始化,错误的原因应该是编译器不知道str6类型是什么,不能对其分配空间;
var str7: String? = "testNull" //不需多解释,str7可为null,同时赋值为"testNull";

解释完变量定义时关于空的概念,接下来就该看看这种保护机制能否真的让我们省心。就拿获取字串的长度为例,Kotlin中String类有个length属性,即调用方式为strObject.length。

有两种形式定义的变量不用担心(1 类型为String且不允许为null;2 类中不包含length属性), 理由很简单,前者不会出现null异常,后者获取length属性在编译阶段就会出错,或者说在敲完代码时编译器就会标红提示了。所以,String类型但允许为null的才需要我们关注,因为这时候有可能出现运行时异常。

对于声明为String?的变量,访问属性时会涉及到问号和双感叹号两个操作符("?"和"!!"),前者表示执行后面代码前先检查变量赋值情况,后者表示不检查而直接访问属性(危险)。

要理解清楚,最好的方法就是让代码说话。

var str2: String? = null
println("str2.length: " + str2.length)  //编译错误,因为之前只是将str2声明为可以是null同时赋值为null,所以紧接着访问其length属性是不允许的;
println("str2?.length: " + str2?.length)  //输出"null",加了问号就会先检查str2的赋值情况,如果是null,就不继续执行后半部分(.length),直接返回null;
println("str2!!.length: " + str2!!.length)  //运行异常,不检查的后果就是通过null引用去访问length属性;
if (str2 != null) {
    println("str2!!.length: " + str2!!.length)  //不会执行到if代码块中,这里用了类似Java中的做法;
}
str2 = "testNull"  //assign
println("str2.length: " + str2.length)  //输出"8",到这里,相比能体会到Kotlin的智能之处了,在第八行对str2赋值之后,就不会再像第二行那样报编译错误了;
println("str2?.length: " + str2?.length)  //print 8
println("str2!!.length: " + str2!!.length)  //print 8
if (str2 != null) {
    println("str2!!.length: " + str2.length)  //print 8
}

那么这里有一个疑问,用"!!"来访问属性是不明智的选择,好像"?"更稳妥一些?毕竟后者在变量是否null的情况下都能做出相应的处理。我所能想到的需要用"!!"的场景之一是:当一个变量在声明时不能马上初始化,而在真正用到时又必须是非null的。这种情况应该并不少见吧,那次此时"!!"就派上用场了。

先举一个简单粗暴的列子:

var str: String? = null
//do something to assign str
val str2: String = str!!

当声明str的时候还需后面的处理结果给它赋值,而声明str2为非null,就必须以str!!的形式才能通过编译。

下面再给出Android中Application类单例化代码,就不做解释了。

class App : Application() {
    companion object {
        private var instance: Application? = null
        fun instance() = instance!!
    }
    override fun onCreate() {
        super.onCreate()
        instance = this
    }
}

类方法扩展

这个特性支持在现有类的基础上扩展方法,特别是系统库中的类,因为如果是我们自定义的类,那么扩展和添加方法没有什么差别。

方法定义

fun getArtict(): Artist? {
    return null
}

Kotlin中是以fun关键字声明方法,没有返回值时不需要在方法名后面写任何类型,默认是Unit类型(可写可不写,但其和null不是一回事,所以不写返回值类型或者写了Unit后不能够返回null)。

扩展

fun String.printStr() {
    println("printStr: " + this)
}

var str = "testExtend"
str.printStr()

上面代码为类String扩展了一个printStr(),这在Java中是不可能的。因为Java中如果既不能改变原有类,又想在其基础上添加方法,就得通过新建类来继承的方式。而现实是Java中只能是单继承,这个机会太珍贵了,更残酷的是有些类还是不能继承的。

代码第5-6行执行结果为:

printStr: testExtend

可见,通过this关键字即可获取到对象(调用者)的值。

lambda

抱歉,lambda后面会专门写文章讲讲

最后想说

这篇文章主要是介绍Kotlin,通过数据类定义、变量空安全、类方法扩展几个方面来和Java做了一个简单的比较,顺带提了Kotlin其他一些基础知识,算是对Kotlin的一个入门,上面提到的知识只是Kotlin的九牛一毛,后面会讲更多。

最后得说明一点,打算利用Kotlin开发应用时必须搞清楚的:Kotlin是基于JVM的。也就是说,尽管编码上和Java相比更简洁,大大提高了开发的效率,但还是和Java一样是运行在JVM中。而且,Kotlin和Java是百分百兼容的,即一个项目中可以同时存在它们的代码,还可以交互。

博主接触Kotlin已经有两三个月,因为之前写Java,所以使用Kotlin基本上没什么障碍,我很少写Android,使用Kotlin都是在开发Spring应用,现阶段与Spring的整合完全能满足需求,为什么使用Kotlin就是因为简洁,写起来爽,语法糖很多,真香!!!