1. 变量与函数

1.1 变量

1.1.1 类型推导

“类型推导”是现在编程语言都喜欢的一个特性,如C++的auto、Swift的let、var、Java 的 var等。Kotlin同样如此,你可以使用val(value)定义常量,var(variable)来定义变量。

对于常量来说,每当定义以后,其值是不可修改的

1
2
3
4
fun main() {
val number = 1 // number 会被推导为 Int
println("number = " + number) // > number = 1
}

如果你要去修改这个 number,那么就会出现错误

1
2
3
4
fun main() {
val number = 1
number = 2 // error: Val cannot be reassigned
}

需要可变的量,我们需要使用 var 来修饰。

1
2
3
4
5
6
7
fun main() {
var number = 1
println("number = " + number) // > number = 1
number = 3
println("number = " + number) // > number = 3
}

1.1.2 数据类型

在上一节的类型推导中,number 会被推导为整型,但是其类型不是 int,而是 Int,Kotlin 同 Python一样,万物皆对象。

如果你熟悉 Java,那么只需要把 Java 的的基本数据类型的首字母大写即可,如

int ——> Int

boolean ——> Boolean

char ——> Char

1.2 函数

1.2.1 标准格式

函数的标准格式如下:

1
2
3
4
5
6
7
8
9
关键字  函数名		  参数列表						返回值类型
fun functionName(parameter: Type): returnType {
函数体
}
// 举个栗子
import kotlin.math.max
fun largerNumber(left: Int, right: Int): Int {
return max(left, right)
}

2. 逻辑控制

2.1 条件语句

2.1.1 if

Kotlin 普通的 if 条件语句和 Java 没什么区别,因此不做多赘述。唯一的不同之处就是,Kotlin 的 if 语句是带有返回值的,其实就相当于三目运算符。比如我们自己实现一下 largerNumber 函数

1
2
3
4
5
6
7
8
9
fun largerNumber(left: Int, right: Int): Int {
var result = if (left < right) {
left
} else {
right
}
return result
}
// 甚至还能再优化下,删除 result 变量, 因为它是多余的

2.1.2 when

Kotlin 多了一个 when 条件语句。这个 when 语句可以理解为升级版的 switch。when 语句 允许传入一个任意类型的参数,然后在 when 的代码块中执行一系列匹配条件的逻辑。其标准格式如下:

1
2
3
4
when (参数) {
匹配值 -> 执行逻辑
else -> 默认逻辑 // 一定要有else,同 switch 的 default
}

举个栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun appSource(appName: String) = when (appName) {
"抖音" -> "字节跳动"
"淘宝" -> "阿里巴巴"
else -> "无" // 一定要有 else
}

// 甚至可以直接执行一个函数
fun print(msg: String) {
println(msg)
}

fun appSource(appName: String) = when (appName) {
"抖音" -> print("字节跳动")
"淘宝" -> print("阿里巴巴")
else -> print("无") // 一定要有 else
}

fun main() {
appSource("抖音") // > 字节跳动
}

2.2 循环语句

2.2.1 for 循环

Kotlin 的 for 循环是一个快速迭代循环,它可以对任何提供迭代器(iterator)的对象进行遍历。标准格式如下:

1
2
3
for (item in obj) {
// ……
}

如何在数组中使用。

1
2
3
for (item: Int in ints) {
// ……
}

如果需要使用到下标。

1
2
3
for (i in array.indices) {
print(array[i])
}

同时其还有步幅、倒序等一系列功能:

1
2
3
4
5
6
7
8
// 倒序
for (i in 4 downTo 1) print(i) // > 4321
// 指定步幅
for (i in 1..4 step 2) print(i) // > 13
// 结合使用
for (i in 4 downTo 1 step 2) print(i) // > 42
// 排除某个item
for (i in 1 until 10) print(i) // > 123456789

3. 面向对象

3.1 封装

3.1.1 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person {
var name = ""
var age = 0
var sex = ""
val nationality = "CN"
fun information() {
println("我叫 $name,性别$sex,今年 $age 岁了。")
}
}

fun main() {
val tom = Person()
tom.name = "Tom Ding"
tom.age = 33
tom.sex = "男"
// Error: Val cannot be reassigned
// tom.nationality = "USA"
tom.information() // > 我叫 Tom Ding,性别男,今年 33 岁了。
}

上面创建了一个 Person 类。通过 var 和 val 来定义属性,var 定义的属性可以更改,val 定义的属性不可更改。创建对象时,也无需使用 new 关键字。Kotlin 本着最简化的设计原则,新生代语言,大多如此。

3.1.2 访问限定

Java 中的函数访问限定符有:public、private、protected、default(不写限定符时)。Kotlin 中的限定修饰符为:public(默认的)、private、protected、internal。

把限定符写在 fun 关键字的前面即可。

1
2
3
4
5
6
7
class Person {
……
internal fun talk(message: String) {
……
}
……
}
关键字 描述
public 模块内外所有类可见(默认的)
private 当前类可见
protected 当前类和子类可见
internal 同一模块的类可见=

3.2 继承

3.2.1 修饰符

在 Kotlin 中,除了 抽象类默认都是不可被继承的,类似 Java 的final 关键字。它的设计思想和 val 一样,不可变的总是安全的。如果我们需要其可以继承,则需要加上 open 关键字。同理,如果需要方法可以被重写,则也要加上 open 关键字。

1
2
3
4
5
6
7
class Person {
……
}

class Student: Person { // This type is final, so it cannot be inherited from

}

那么现在加上了 open 关键字后,仍然会有报错

1
2
3
4
5
6
7
open class Person {
……
}

class Student: Person { // This type has a constructor, and thus must be initialized here

}

这是由于 Kotlin 在继承的时候,同构造函数关联在一起了。

3.2.2 主构造函数

在初学Kotlin 的构造函数时,它的规则可以和 swift 媲美了。

同时如果你有 C++ 的经验,那么理解起来相对要容易些。

1)每个类会默认生成一个不带任何参数的主构造函数,如果你显示的声明了参数,那么在构造的时候同理要有实参;每个类只有一个主构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Animal(val name: String) {
……
}

fun main() {
val tiger = Animal("老虎")
// 不能 Animal(),因为其主构造函数是带有参数的
}

/*



*/

2)主构造函数没有函数体,如果需要写逻辑再构造函数中,Kotlin 提供了 init 代码块

这里需要关注一点:

主构造函数的参数列表中,如果没有加 val 或 var 声明这个属性,那么这个参数只是构造函数中的一个形参,它不是属性,你可以在 init 初始化块中使用,然后赋值给在类中属性。

1
2
3
4
5
6
7
8
9
// name 是形参,你必须再声明一个属性
// age 和 sex 则是属性
open class Person(name: String, val age: Int, val sex: String) {
var name = ""
init {
this.name = name // 可以在这里赋值
…… // 其他逻辑
}
}

3)在继承中,子类必须调用父类的构造函数;一般情况下,我们在继承的时候会直接调用

1
2
3
4
5
// 对于 name  age  sex 我们不必再添加 val / var 
// 因为 Person 类已经声明了该属性,我们再添加 val / var 会覆盖父类的属性
class Student(val number: String, name: String, age: Int, sex: String): Person(name, age, sex) {

}

Note:

i. 一般情况下我们在类里面声明属性,如果实在需要约束构造参数则在主构造参数列表中声明,否则默认构造函数即可。

ii. 构造函数声明参数时注意 val 和 var 的使用

iii. 子类在继承时,如果有主构造函数,则需要调用父类的主构造函数。

当一个类没有主构造函数,但是有次构造函数时可以不用调用主构造函数,因此可以不加 “()”

次构造函数基本使用不到,它就是给构造函数的参数列表加默认值的,因为Kotlin有函数默认值这一功能,要了解的可以查看官方文档。

1
2
3
4
5
6
7
class Student: Person { // 没有主构造函数,这里即不需要加括号

// constructor 声明一个无参的次构造函数
// 由于本类没有主构造函数,因此直接调用父类的主构造函数

constructor(): super("Tom Ding", 33, "男")
}

综上,其实多用用就知道怎么回事了。“纸上得来终觉浅,绝知此事要躬行。”。一般参照「Note的第一条 」

3.3 多态

3.3.1 接口

Kotlin 和 Java 一样使用 interface 关键字来定义接口。接口中的函数可以有默认实现,也可以没有。

下面定义一个接口:

1
2
3
4
5
6
interface Homowork {
fun doHomeWork() {
// nothing
}
fun readBooks()
}

下面需要创建子类去实现接口,Kotlin 实现接口和继承一样使用冒号即可。

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
class Child: Student(), Homowork {
override fun doHomeWork() {
println("我是小孩子,没有家庭作业")
}
override fun readBooks() {
println("我是小孩子,我看漫画书")
}
}

class SeniorStudent: Student(), Homowork {
override fun doHomeWork() {
println("T_T 我是高中生,我做三年高考五年模拟")
}
override fun readBooks() {
println("我是高中生,我看瓦尔登湖")
}
}

// 多态
fun doHomowork(student: Homowork) {
student.doHomeWork()
student.readBooks()
}

fun main() {
val s1 = Child()
doHomowork(s1) // > 我是小孩子,没有家庭作业
// > 我是小孩子,我看漫画书
val s2 = SeniorStudent()
doHomowork(s2) // > T_T 我是高中生,我做三年高考五年模拟
// > 我是高中生,我看瓦尔登湖
}

3.4 数据类与单例

3.4.1 数据类

系统架构中,一定缺少不了数据。因为规范的数据类也是不可避免的。在 Java 中,通常需要重新 equals()、hashcode()、toString()等方法。在 Kotlin 中,只需要在 创建数据类时使用 data 关键字来表示这是一个数据类,Kotlin 会根据主构造函数自动生成上面的函数。

1
2
3
4
data class Books(val name: String, val price: Int) {
// 一定要有主构造函数
// Data class must have at least one primary constructor parameter
}

举个栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
data class Books(val name: String, val price: Int) { }

fun booksTest() {
val book1 = Books("瓦尔登湖", 99)
val book2 = Books("小王子", 88)
val book3 = Books("瓦尔登湖", 99)

println("$book1, $book2, $book3")
println("book1 == book3: " + (book1 == book3) +"\nbook1 == book2: "+ (book1 == book2))
}
// > Books(name=瓦尔登湖, price=99), Books(name=小王子, price=88), Books(name=瓦尔登湖, price=99)
// > book1 == book3: true
// > book1 == book2: false

3.4.2 单例

相信其他编程语言的单例编写已经顺手拈来了。Kotlin 的单例更是极为简单。如同数据类一样,你只需要使用 object 关键字,即可声明一个单例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
object Singleton {
init {
// 在这里做一些初始化的逻辑
println("Singleton init")
}
fun action() {
println("我自己 $this")
}
}

fun singletonTest() {
Singleton.action()

Singleton.action()

Singleton.action()
}
// > Singleton init
// > 我自己 com.example.myapplication.Singleton@1d251891
// > 我自己 com.example.myapplication.Singleton@1d251891
// > 我自己 com.example.myapplication.Singleton@1d251891

Note:

i. 单例不能有主构造函数:Constructors are not allowed for objects

ii. 单例的调用和静态方法调用是一样的,背后是 Kotlin 自动创建一个单例,而且全局仅存在一个,可以看到上面的测试,仅初始化一次

4. 函数式编程与可选类型

4.1 lambda表达式

4.1.1 标准格式

现在的编程语言基本都支持了 lambda 表达式,比如C++、OC、Java8。lambda 是函数式编程的产物。在 iOS 中有个 Masonry 自动布局框架是一个非常经典的函数式编程实现。

Note:函数其实也是有类型的,因此我们可以做到函数作为参数、返回值、函数变量等等

在 Kotlin 中,lambda 表达式的标准格式如下:

lambda 表达式主体斗志在大括号中

1
2
3
4
5
6
7
8
// 无参
val/var 变量名 = { 代码块 }

// 有参数
val/var 变量名 : (参数列表) -> 返回值类型 = {参数列表 -> 代码块 }
// 可以如下简化
val/var 变量名 = {参数列表 -> 代码块 }

4.1.2 进阶

下面是 lambda 的一些进阶技巧,用一个测试代码来说明:

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
fun lambdaTest() {
// 水果列表
// Note:关于 Kotlin 的集合建议实操练习
val fruits = listOf<String>("apple", "banana", "orange", "pear", "grape", "lemon", "watermelon", "strawberry")
// 创建 lambda
val action = {fruit: String -> fruit.length }
// 求得结果
val result = fruits.maxBy(action)

println(result) // > watermelon

// 简化步骤
// 1. 不需要建立多余的 action 变量
var result2 = fruits.maxBy {fruit: String -> fruit.length }
println(result2) // > watermelon

// 2. lambda 参数列表可以进行推导,因此可以省去类型
result2 = fruits.maxBy {fruit -> fruit.length }
println(result2) // > watermelon

// 3. 当lambda 只有一个参数时,我们可以使用 it 代替
// it 可表示为单个参数的隐式名称
result2 = fruits.maxBy { it.length }
println(result2) // > watermelon
}

更多技巧可以在后续深入了解 Kotlin 的时候学习和掌握

4.2 可选类型

4.2.1 空指针检查

这里的二级标题之所以是「可选类型」,是它让我想到了 swift 语言的可选类型。它们两个的概念是一模一样的。表示可以为空类型、可选解包、强制解包等。

空指针类型在日常开发工程中是非常常见的一个问题。在 C++ 或者 Java 中你去解引用 空指针是危险的行为。

在 Kotlin 中所有的函数参数都是不可以为空的,除非我们要求它是一个可空类型。

可空类型就是在 类型的后面加一个 问号。例如:Int?、String?

还是用代码解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Box {
fun talk() {
println("我是一个箱子")
}

}
fun optionalTest(box: Box?) {
// Error:Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Box?
// box.talk()

// 可选解包:如果不是空的,我们就执行后面的操作。否则返回 null
box?.talk()

// 对于我们确定它是非空的,那么我们可以直接强制解包
// 强制解包:确信其不是空,可以执行后面的操作
// Note:这是危险的,因为外部的判断是未知的,即使这是你写的代码,也无法保证后续不会有人去删除这个安全检查
fun bbox(box: Box?) {
box!!.talk()
}
if (box != null) {
bbox(box)
}
}

4.2.2 判空辅助工具

其实上面的 ?. 和 !! 都属于辅助的判空工具。下面是 OC 中也有的一个符号 ?:

一个二元操作符:left ?: right

如果左边不为空则返回左边的值,否则返回右边的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Box {
fun tag(): String {
return "集装箱"
}
}
fun optionalTest(box: Box?) {
println(box?.tag() ?: "空箱子")
}
fun main() {
var box: Box? = null
optionalTest(box) // > 空箱子
box = Box()
optionalTest(box) // > 集装箱
}

5. 总结

到这里 Kotlin 入门教程就结束了。上面的教程已经足够进入写一些简单的代码了。

对于学习安卓开发我们还需要知道安卓开发流程、应用命中周期、UI控件、布局、设计模式等等。

语言只是其中一小部分,先入门后精通!

当然除了 Kotlin,还需要掌握 Java 语言!

加油!