本文将伴随大家进入Kotlin语言的正式学习生涯中,希望大家不要半途而废哦!笔者将Kotlin用于Android开发中,因此将从Android开发的视角叙述相关内容,同时将与Java语言有所联系。

1. 正式进入开发的准备知识

1.1 声明包名及导入包

包,含义、关键字、使用方式与Java一致,如在包com.my.demo下,导入包android.os.Bundle,代码如下:

1
2
3
4
5
6
//源文件顶部
//package关键字声明包名
package com.my.demo

//import关键字导入所需包
import android.os.Bundle

1.2 程序入口

每个程序都需要一个入口,一个main函数是必不可少的。当然,在Android开发中不需要main函数,而是由系统调用Application和Activity来触发程序的入口。Kotlin的main函数较为简单,代码如下:

1
2
3
4
5
//fun关键字声明一个函数/方法
fun main() {
//函数体/方法体,如打印Hello World!文字
println("Hello World!")
}

1.3 行注释及块注释

在前面的代码中,我用到了许多注释,用来解释说明我写的代码。日常开发中,注释是很有必要的,代码将被你逐渐遗忘,原本的含义也许不易通过代码去理解,良好的注释不仅方便了自己也帮助了别人。在我们的代码中,注释有两种,行注释与块注释,Kotlin中的注释与Java类似,代码示例如下:

1
2
3
4
5
6
7
8
9
//这是一条行注释
println("Hello World!") //行末也可以使用行注释

/*这是块注释
块注释可以包含多行*/

/*块注释也可在一行中使用*/

println(/*块注释也可以插入代码中使用*/"Hello World!")

1.4 代码风格

Kotlin的代码风格类似多种语言的结合体,可以看到很多Java、C#、C++等语言的影子,官方有提供代码风格指南:

应用风格指南
如需根据本风格指南配置 IntelliJ 格式化程序,请安装 Kotlin 插件 1.2.20 或更高版本,转到 Settings | Editor | Code Style | Kotlin,点击右上角的 Set from… 链接,并从菜单中选择 Predefined style | Kotlin style guide。
如需验证代码已按风格指南格式化,请转到探查设置(Inspections)并启用 Kotlin | Style issues | File is not formatted according to project settings 探查项。 验证风格指南中描述的其他问题(如命名约定)的附加探查项默认已启用。

包名采用全小写字母(例:package com.my.demopackage),类名采用首字母大写的驼峰式命名(例:class SplahActivity{}),函数名及变量名采用首字母小写的驼峰式命名(例:fun doSomething(){}),常量名采用大写字母与下划线结合的方式(例:const val MAX_COUNT = 8)。

Kotlin语句末尾无需;标识,因此换行是代表语句结束还是未完待续就需要我们仔细观察了。其他一些不影响代码运行的风格就不一一例举了,可以参考官方文档,希望大家好好写代码,不只是关注于功能、性能,还要注意代码的可读性,写出漂亮的代码!

1.5 是否需要取代findViewById()方法

Kotlin在Android开发中有一个自动绑定View控件的特性,很多人都推崇取代findViewById()方法,但在使用中发现,这个特性的适用性不高。

Kotlin可以直接在代码中通过id来使用View控件,如:在SplashActivity的布局activity_splash_layout.xml中,存在一个id为tv_hello_world的TextView,那么在SplashActivity的onCreate()方法中可以使用语句tv_hello_world.setText("Hello World!"),无需提前声明一个TextView变量,再经过findViewById()来绑定控件。

但如果是需要同一个类配合多个布局,且布局中存在相同id的控件时,此特性将无法使用;或是ListView、RecyclerView中存在多个Item类型对应不同布局,且布局中存在相同id控件的情况,此特性也无法使用。

此特性还存在可读性问题,布局中id均使用小写字母与下划线结合的方式命名,在类文件中直接当作变量使用,违背了变量采用驼峰式命名的代码风格,并且找不到该变量的声明,使得代码逻辑不易于理解。

综述,个人意见为尽量还是使用findViewById()方法,毕竟我们的代码不能只想着省事。

2. 基本语法结构

2.1 函数

函数的声明关键字是fun,结束语句是return some,空返回值类型是Unit,返回值类型为Unit时结束语句是return且返回值声明及结束语句都可以省略。基本结构是fun 方法名(入参1名: 入参1类型, 入参2名: 入参2类型): 返回值类型{ 方法体 },代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//带有两个 Int 参数、返回 Int 的函数
fun sum(a: Int, b: Int): Int {
return a + b
}

//将表达式作为函数体、返回值类型自动推断的函数
fun sum(a: Int, b: Int) = a + b

//函数返回无意义的值
fun printSum(a: Int, b: Int): Unit {
println("sum of $a and $b is ${a + b}")
return
}

//Unit 返回类型可以省略
fun printSum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}

2.2 变量

变量的声明关键字有两个,varval,其中val修饰的变量只能被赋值一次,类似Java中被final修饰的变量。一个变量的声明基本结构是var/val 变量名: 变量类型,声明语句后可直接跟= 值进行初始赋值并且此时能够自动推断变量类型(可省略: 变量类型),代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//定义只读局部变量使用关键字 val 定义,只能为其赋值一次
val a: Int = 1 // 立即赋值
val b = 2 // 自动推断出 `Int` 类型
val c: Int // 如果没有初始值类型不能省略
c = 3 // 明确赋值

//可重新赋值的变量使用 var 关键字
var x = 5 // 自动推断出 `Int` 类型
x += 1

//顶层变量(成员变量)
val PI = 3.14
var x = 0

fun incrementX() {
x += 1
}

2.3 字符串模板

在Java中,字符串要拼接上变量值或是方法的返回值比较不友好,要么用+连接,要么就得使用String.format()方法。在Kotlin中可以在字符串中使用$变量名或是${表达式}的方式在字符串中插入变量或是表达式,代码示例如下:

1
2
3
4
5
6
7
var a = 1
// 模板中的简单名称
val s1 = "a is $a"

a = 2
// 模板中的任意表达式
val s2 = "${s1.replace("is", "was")}, but now is $a"

2.4 空值与Null检测

Kotlin著名的空安全使用起来还是很烧脑的,变量默认是不会为空的,未赋值的变量在编译中是会报错的,当某个变量的值可以为null的时候,必须在声明处的类型后添加?来标识该引用可为空,但在使用时就需要进行判空操作了。具体的空安全可以看官方文档,这里就先简单说一下,以后可能会单独写一篇空安全的分析。代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//如果 str 的内容不是数字返回 null
fun parseInt(str: String): Int? {
// ……
}

//使用返回可空值的函数
fun printProduct(arg1: String, arg2: String) {
val x = parseInt(arg1)
val y = parseInt(arg2)
// 直接使用 `x * y` 会导致编译错误,因为它们可能为 null
if (x != null && y != null) {
// 在空检测后,x 与 y 会自动转换为非空值(non-nullable)
println(x * y)
}
else {
println("'$arg1' or '$arg2' is not a number")
}
}

//或者
fun printProduct(arg1: String, arg2: String) {
val x = parseInt(arg1)
val y = parseInt(arg2)
if (x == null) {
println("Wrong number format in arg1: '$arg1'")
return
}
if (y == null) {
println("Wrong number format in arg2: '$arg2'")
return
}
// 在空检测后,x 与 y 会自动转换为非空值
println(x * y)
}

2.5 类型检测与自动类型转换

Kotlin这一特性用起来相当舒服,is运算符检测一个表达式是否某类型的一个实例,如果一个不可变的局部变量或属性已经判断出为某类型,那么检测后的分支中可以直接当作该类型使用,无需显式转换,代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// `obj` 在该条件分支内自动转换成 `String`
return obj.length
}
// 在离开类型检测分支后,`obj` 仍然是 `Any` 类型
return null
}

//或者
fun getStringLength(obj: Any): Int? {
if (obj !is String) return null
// `obj` 在这一分支自动转换为 `String`
return obj.length
}

//甚至
fun getStringLength(obj: Any): Int? {
// `obj` 在 `&&` 右边自动转换成 `String` 类型
if (obj is String && obj.length > 0) {
return obj.length
}
return null
}

2.6 条件表达式

if语句在Kotlin中基本与Java一致,唯一的不同点在于最后一行代码若是单独的值,将作为if语句的返回值,这一特点用来取代了Java中的a? b : c三元表达式,示例代码如下:

1
2
3
4
5
6
7
8
9
10
fun maxOf(a: Int, b: Int): Int {
if (a > b) {
return a
} else {
return b
}
}

//if 也可以用作表达式
fun maxOf(a: Int, b: Int) = if (a > b) a else b

2.7 循环表达式

2.7.1 for 循环

Kotlin中的for循环有点像Java中的foreach语句,基本结构为for (对象 in 列表/数组/区间) { 循环体 },代码示例如下:

1
2
3
4
5
6
7
8
9
10
val items = listOf("apple", "banana", "kiwifruit")
for (item in items) {
println(item)
}

//或者
val items = listOf("apple", "banana", "kiwifruit")
for (index in items.indices) {
println("item at $index is ${items[index]}")
}

2.7.2 while 循环

Kotlin中的while循环与Java一致,基本结构为while (循环条件) { 循环体 },代码示例如下:

1
2
3
4
5
6
val items = listOf("apple", "banana", "kiwifruit")
var index = 0
while (index < items.size) {
println("item at $index is ${items[index]}")
index++
}

2.8 when 表达式

when表达式类似Java中的switch语句,但是比switch更高级,能够完全取代多个elseif的情况,基本结构为when (变量) { 条件 -> 操作 },示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//基本使用,类似switch
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 注意这个块
print("x is neither 1 nor 2")
}
}

//我们可以用任意表达式(而不只是常量)作为分支条件
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}

//我们也可以检测一个值在(in)或者不在(!in)一个区间或者集合中
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}

//另一种可能性是检测一个值是(is)或者不是(!is)一个特定类型的值。注意: 由于智能转换,你可以访问该类型的方法与属性而无需任何额外的检测。
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}

//when 也可以用来取代 if-else if链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}

2.9 区间

区间这个概念类似于数学中的区间(适用于整数,假设有整数a和b,a<b),形式有三种:

  1. a..b,即从a到b之间的所有整数的升序排列,包括a和b,相当于数学中的[a,b]

  2. b downTo a,即从a到b之间的所有整数的降序排列,包括a和b,可以理解成[b,a]

  3. a until b,即从a到b之间的所有整数的升序排列,包括a但不包括b,相当于数学中的[a,b)

同时,还可以在末尾加上step 整数来指定步长,例如1..5 step 2代表数列{1, 3, 5}。区间一般用于for循环,代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
for (i in 1..4) print(i)
//输出1234

for (i in 4 downTo 1) print(i)
//输出4321

for (i in 1 until 4) print(i)
//输出123

for (i in 1..8 step 2) print(i)
//输出1357
`

2.10 创建实例

实例的创建,没有关键字的标识,就像函数的调用。常用操作是声明一个变量,赋值为某个对象实例,调用其构造方法,例如val rectangle = Rectangle(5.0, 2.0),相对于Java来说,省略了new关键字,就像调用了一个返回值类型为Rectangle的方法。