Kotlin笔记

Kotlin 与 Java 的关系

Java运行过程:Java源代码 -> 编译 -> 生成class文件 -> JVM解释执行

Java虚拟机并不关心class文件是如何生成的,因此也可以使用Kotlin生成class文件,JVM也可以解释执行。

Kotlin可以无缝使用Java第三方的开源库。

Q:Android为什么推荐Kotlin?Kotlin与Java比有哪些优势?

A:

  1. 空安全
  2. Lambda表达式和高阶函数
  3. Kotlin更简洁,引入了数据类、get、set方法等
  4. 拓展函数
  5. 支持协程
  6. 完全兼容Java

val 与 var

类比JavaScript中的constlet

val用于声明常量,相当于加了final,赋值后无法修改;var用于声明变量。

Any、Unit

  • Any等同于Java中的Object,是所有类的父类
  • Unit等同于void,当函数无返回值时会自动加上返回值Unit

if

Kotlin中的if是带有返回值的,返回值即花括号内的最后一行表达式的值:

1
2
3
4
5
val max = if(a > b) {
a
} else {
b
}

字符串

字符串模板

$后花括号内可以是一个表达式:

1
2
val str = "你好"
println("$str${str.replace("你好", "再见")}!") // 你好,再见!

多行字符串

三对双引号内的字符串可换行:

1
2
3
4
5
6
7
8
val poem = """
关关雎鸠,
在河之洲。
窈窕淑女,
君子好逑。
""".trimIndent()

println(poem)

其中trimIndent函数的作用是去除输入行的公共最小缩进(空行不影响)。输出为

1
2
3
4
关关雎鸠,
在河之洲。
窈窕淑女,
君子好逑。

有时为了让代码更清晰,我们可以使用|符号结合trimMargin函数来标记实际字符串的开始位置:

1
2
3
4
5
6
7
8
val poem = """
| 蒹葭苍苍,
|白露为霜。
| 所谓伊人,
|在水一方。
""".trimMargin()

println(poem)

其中trimMargin函数的作用是将多行字符串中的每行内容使用特定的前导字符(默认为|)进行标记,然后去除这些字符及其前面的空白。输出为

1
2
3
4
  蒹葭苍苍,
白露为霜。
所谓伊人,
在水一方。

也可指定不同的前导字符:

1
2
3
4
5
6
7
8
val poem = """
>郎骑竹马来,
> 绕床弄青梅。
>同居长干里,
> 两小无嫌猜。
""".trimMargin(">")

println(poem)

when

相当于switch,不过用起来更方便。

1
2
3
4
5
6
7
fun getScore(name: String) = when(name) {
"张三" -> 85
"李四" -> {
return 91
}
else -> 0
}

区间

闭区间

1
val range = 0..10   // [0, 10]

左闭右开区间

1
val range = 0 until 10   // [0, 10)

降序闭区间

1
val range = 10 downTo 0   // [10, 0]

循环

for - in 循环

1
2
3
4
5
6
7
8
9
10
// 输出 0~9
for (i in 0 until 10) {
println(i)
}

// 每次循环 i+=2
for (i in 0..10 step 2) {
println(i)
}
// 输出 0~10 内的偶数

构造函数

Kotlin中有主次构造函数之分。

主构造函数没有函数体,直接定义在类名的后面。若想在主构造函数中编写一些逻辑,将代码写在init中:

1
2
3
4
5
6
7
class Student(val name, val grade) {
init {
// 主构造函数的逻辑写在这里
}
}

Student('张三', 86)

次构造函数有函数体,且必须调用主构造函数。

1
2
3
4
5
6
7
8
9
10
11
class Student(val name, val grade) {
init {
// 主构造函数的逻辑写在这里
}

constructor(name: String) : this(name, 0) {
// 次构造函数逻辑
}
}

Student('张三', 86)

继承

Kotlin中非抽象类默认都是不可以被继承的,相当于给类加上了final。要声明为可继承类,需要加上open关键字。

同时子类中的构造函数必须调用父类中的构造函数。

1
2
3
4
5
6
7
8
9
10
11
open class Person(val name) {
//TODO
}

class Student(val name, val grade) : Person(name) {
init {
// 主构造函数的逻辑写在这里
}
}

Student('张三', 86)

接口

1
2
3
4
5
6
7
8
9
10
interface Rectangle {
fun getPerimeter()
fun getArea()
}

// Square类继承Shape类,同时实现Rectangle接口
class Square(edge: Double) : Shape(), Rectangle {
override fun getPerimeter() = edge * 4
override fun getArea() = edge * edge
}

单例

Java中实现单例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Singleton {
private static Singleton instance;

private Singleton() {}

public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}

public void singletonTest() {
System.out.println("singletonTest方法被调用");
}
}

Singleton singleton = Singleton.getInstance();
singleton.singletonTest();

Kotlin中实现单例十分简便,只需要将关键字class改为object即可:

1
2
3
4
5
6
7
object Singleton {
fun singletonTest() {
println("singletonTest is called.")
}
}

Singleton.singletonTest()

拓展函数

我们可以为某一个数据类型添加自定义的方法。

举个例子,如对时间进行格式化,一种普通做法是写一个工具类,用伴生对象实现类似静态方法的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class DateTimeUtil {
companion object {
private const val DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"

// 格式化时间
fun format(dateTime: LocalDateTime): String {
val formatter = DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)
return formatter.format(dateTime)
}
}
}

fun main() {
val dateTime = LocalDateTime.now()
println("$dateTime") // 2024-03-24T23:07:39

val formattedDateTime = DateTimeUtil.format(dateTime)
println("$formattedDateTime") // 2024-03-24 23:07:39
}

除此之外还有另一种更为优雅的方法:

1
2
3
4
5
6
7
8
9
10
11
fun LocalDateTime.format(): String {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
return formatter.format(this)
}

fun main() {
val dateTime = LocalDateTime.now()
println("$dateTime") // 2024-03-24T23:07:39

println(dateTime.format()) // 2024-03-24 23:07:39
}

这里调用的format函数是我们自定义的。

kotlin与dart都有这种特性。

可见性

可见性

List、Set、Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// List
val list = listOf("春秋左氏传", "春秋公羊传", "春秋谷梁传") // 不可变列表,不可添加元素
val mutableList = mutableListOf("诗经", "尚书", "礼记", "易经") // 可变列表
mutableList.add("春秋")

// Set
val set = setOf("白起", "王翦", "廉颇", "李牧")
val mutableSet = mutableSetOf("齐桓公", "晋文公", "秦穆公", "宋襄公")
mutableSet.add("楚庄王")

// Map
val map = mapOf("儒" to "孔子", "法" to "韩非子", "墨" to "墨子", "道" to "庄子")
val mutableMap = mutableMapOf("齐" to "临淄", "楚" to "郢都", "燕" to "蓟城", "韩" to "新郑", "赵" to "邯郸", "魏" to "大梁")
mutableList.add("秦" to "咸阳")

Lambda表达式

即可当做参数传递的一段代码。

完整结构:val lambda = { param1: Type, param2: Type -> 函数体 },最后一行表达式的值作为整个Lambda表达式的返回值。

1
2
3
4
5
6
7
8
9
10
button.setOnClickListener({ data -> println(data) })

// 若Lambda表达式是函数的最后一个参数,可以把Lambda表达式提到括号外:
button.setOnClickListener() { data -> println(data) }

// 若Lambda表达式是唯一的参数可以省略括号:
button.setOnClickListener { data -> println(data) }

// 若Lambda表达式内只有一个参数,可以用`it`代指:
button.setOnClickListener { println(it) }