变量和函数
变量
定义变量
Kotlin 中定义变量需要使用两种关键字:val 和 var。
val 是 value 的简写,用来声明一个不可变的变量,也就是变量初始被赋值之后,不能被重新赋值,在 Java 中就是在变量前加上了 final 关键字。
var 是 variable 的简写,用来声明一个可变的变量,变量初始被赋值之后,还是可以随便修改它的值。
我们只需这样就能声明一个不可变的变量:
fun main(){
val a = 10
println("a = $a")
}
这里我们并不用显式地指定变量的类型,因为 Kotlin 具有类型推导机制,它会根据变量的赋值操作,来判断变量的类型。
然而,有些情况下,我们还是需要显式地指定变量的类型。
比如一个延迟初始化的变量(变量声明时没有立即被赋值,你可以先不关心它是什么),Kotlin 就无法推导出这个变量的类型。又或者为了代码的可读性或者特殊需要,我们也要显式声明变量的类型,就像这样:
fun main() {
val a: Short = 10 // 显式指定为 Short 类型,否则会被推导为 Int 类型
println("a = $a")
}
数据类型
Kotlin 其实是没有 Java 的基本类型的,所有数据类型都是对象数据类型。
比如刚刚的 Int 实际上是一个类:
public class Int private constructor() : Number(), Comparable<Int> {
// ..
}
那我们来看看 Java 中的基本数据类型在 Kotlin 中对应的对象数据类型:
| Java基本数据类型 | Kotlin对象数据类型 |
|---|---|
| int | Int |
| long | Long |
| short | Short |
| float | Float |
| double | Double |
| boolean | Boolean |
| char | Char |
| byte | Byte |
从上到下分别为:整型、长整型、短整型、单精度浮点型、双精度浮点型、布尔型、字符型、字节型。
val 和 var 的选择
现在我们修改不可变变量的值,比如乘以 2 倍:
fun main() {
val a: Int = 10
a = a * 2 // 错误
println("a = $a")
}
发现报错了:Val cannot be reassigned
这是因为 a 是不可变的变量,不能被重新赋值,所以我们就需要改为可变变量,只需使用 var 关键字声明变量:
fun main() {
var a: Int = 10
a *= 2
println("a = $a")
}
这样就行了,运行结果:
那现在你可能会有一个问题,val 连修改变量的值都不行,为什么要用它?
其实 val 的出现是为了解决 Java 中 final 关键字没有被正确使用的问题。
因为在 Java 中,你不知道一个可变变量会在什么时候被谁修改了,即使它本不应该被修改,这样就会导致一些很难排查的问题。因此除非一个变量明确允许被修改,否则都应该给它加上 final 关键字。
但不是每个程序员都有这种意识的,导致 Java 程序员几乎都没有主动使用过 final。而 Kotlin 意识到了这点,在设计时就提供了 var 和val 两种不同的声明变量的关键字,让我们在声明变量的时候就进行思考:该变量是可变的还是不可变的。
那什么时候该用 val,什么时候又该用 var 呢?
使用原则:能用 val 就使用 val 来声明变量,保证变量不可被修改。当 val 确实不能满足需求时(变量需要被改变),再使用 var。
函数
定义和调用
前面我们看到的 main 函数也是一个函数,只不过是一个特殊的函数,它是程序的入口,程序运行时,就是从 main 函数开始执行的。
自定义函数的语法规则:
fun methodName(param1: Int, param2: Int): Int {
return 0
}
解释一下:
-
fun 是定义函数的关键字
-
methodName 是函数名
-
括号里声明的是该函数接收什么参数,每个参数的声明格式是“变量名: 参数类型”,参数之间使用逗号隔开。如果不需要接收任何参数,写一个空括号就行了
-
括号后的那部分是可选的,用于声明函数的返回类型。如果函数没有实际的返回值,可以直接不写。也可以写上 : Unit,因为没有返回值的话,默认就是返回 Unit,Unit 类似于 Java 中的 void,只不过 Unit 不是关键字,而是一个单例对象。
-
大括号{}内部的就是函数体,我们在这里编写函数的具体逻辑
现在我们就来定义一个函数:
fun sum(num1: Int, num2: Int): Int {
val sum = num1 + num2 // 求和
return sum // 返回结果
}
这里定义了一个函数名为 sum 的函数,接收两个整型参数,然后返回这两个参数之和。
怎么调用这个函数呢?
格式是 “函数名(传入的参数值)”,这里我们在 main 函数中调用:
fun main() {
val a = 10
val b = 20
val sum = sum(a, b)
println("a + b = $sum")
}
fun sum(num1: Int, num2: Int): Int {
val sum = num1 + num2
return sum
}
运行结果:
语法糖
Kotlin 为函数提供了一种简洁的语法形式,称为“单表达式函数”(Single-Expression functions)。当一个函数的函数体中只有一行代码(一个表达式)时,我们可以不用写函数体 {} 和 return 关键字,将这行代码放在函数定义的尾部,然后中间使用 = 等号进行连接即可。
比如我们刚刚的函数的逻辑,是可以简化为一行代码的:
fun sum(num1: Int, num2: Int): Int {
return num1 + num2
}
使用单表达式函数语法糖:
fun sum(num1: Int, num2: Int): Int = num1 + num2
然后由于 Kotlin 的自动类型推导机制,如果函数的返回类型可以通过赋值操作推导出来,我们可以不显式声明函数的返回值类型,代码进一步简化:
fun sum(num1: Int, num2: Int) = num1 + num2
程序的逻辑控制
程序的执行语句主要有三种:顺序语句、条件语句和循环语句。顺序语句就是代码一行一行、从上到下依次执行,我们之前写的代码都是顺序语句。
但有时我们的逻辑更为复杂,需要根据不同条件来执行不同代码,或者重复地执行某段代码时,就需要使用条件语句和循环语句。
if 条件语句
举个例子,我们要在函数中实现返回两个参数中的较大值的功能,可以这样写:
fun largeNumber(num1: Int, num2: Int): Int {
var large = 0 // 较大值
if (num1 > num2) {
large = num1
} else {
large = num2
}
return large
}
这时你会发现有警告:'Assignment' can be lifted out of 'if',意思是“赋值操作可以从 if 语句中提取出来”。
我们点击自动修复后,发现代码变为了这样:
fun largeNumber(num1: Int, num2: Int): Int {
var large = 0
large = if (num1 > num2) {
num1
} else {
num2
}
return large
}
这是因为 Kotlin 中的 if 语句是一个表达式,本身是有返回值的。它的返回值就是每一个条件分支中的最后一行代码的执行结果。所以代码可以简化为这种形式。
我们还可以将 large 变量的初始化和赋值合并简化为一条语句,并且由于这样修改后,large 没有被重新赋值了,所以可以使用 val 来声明:
fun largeNumber(num1: Int, num2: Int): Int {
val large = if (num1 > num2) {
num1
} else {
num2
}
return large
}
再仔细观察,你会发现 large 其实是一个多余的变量,我们可以直接返回 if 表达式的结果,代码进一步简化:
fun largeNumber(num1: Int, num2: Int): Int {
return if (num1 > num2) {
num1
} else {
num2
}
}
回顾一下函数的语法糖(单表达式函数):当一个函数的函数体只有一行代码时,可以省略函数体部分,直接将这一 行代码使用等号连在函数定义的尾部。
代码能够进一步精简:
fun largeNumber(num1: Int, num2: Int): Int = if (num1 > num2) {
num1
} else {
num2
}
看到这里,你可能会觉得,if-else 语句不是有多行代码?怎么这也能使用单表达式函数的语法糖?
非也,因为 if 语句连同它的其他分支都只算作一个表达式,只有一个执行结果。
最后,上述代码还不是最精简的,当 if 的条件代码块中只有一行代码时,可以省略大括号,所以最终结果是:
fun largeNumber(num1: Int, num2: Int): Int = if (num1 > num2) num1 else num2
when条件语句
when 语句类似于 Java 中的 switch,但在功能上远比它强大和灵活。
精确匹配
比如现在要编写一个函数,根据输入的星期名称,返回这天是工作日还是周末,我们先用 if 语句来完成:
fun getDayType(dayOfWeek: String): String = if (dayOfWeek == "Monday") {
"Weekday"
} else if (dayOfWeek == "Tuesday") {
"Weekday"
} else if (dayOfWeek == "Wednesday") {
"Weekday"
} else if (dayOfWeek == "Thursday") {
"Weekday"
} else if (dayOfWeek == "Friday") {
"Weekday"
} else if (dayOfWeek == "Saturday") {
"Weekend"
} else if (dayOfWeek == "Sunday") {
"Weekend"
} else {
"Unknown day"
}
虽然上述的代码能够实现我们想要的功能,但是有点冗余了,好多 if 和 else。
所以对于这种多判断条件的情况,我们可以考虑使用 when 语句,修改后的代码:
fun getDayType(dayOfWeek: String): String = when (dayOfWeek) {
"Saturday", "Sunday" -> "Weekend"
"Monday", "Tuesday", "Wednesday", "Thursday", "Friday" -> "Weekday"
else -> "Unknown day"
}
并且我们可以看到 when 语句和 if 语句一样,也是一个表达式有返回值,所以我们也可以使用单表达式函数的语法糖,就像上面那样。
when 语句允许传入一个任意类型的参数(称为“主体” subject),然后在 when 的内部定义每个分支条件的格式是:
匹配值 -> { 执行逻辑 }
当执行逻辑的代码只有一行(一个表达式)时,大括号可以省略。
类型匹配
除了上述的精确值匹配外,when 还可以进行类型匹配。比如编写一个判断变量是不是整型的函数:
fun checkInteger(num: Any) { // 参数类型为 Any,表示可以接收任何的类型
when (num) {
is Byte, is Short, is Int, is Long -> println("this is a integer")
else -> println("this is not a integer")
}
}
我们来在main函数中测试一下:
fun main() {
val age: Short = 15
checkInteger(age)
val userId: Int = 1024
checkInteger(userId)
val timestamp: Long = System.currentTimeMillis()
checkInteger(timestamp)
val price: Float = 19.5f
checkInteger(price)
val message: String = "kotlin!"
checkInteger(message)
}
运行结果:
不带参数的 when
when 语句还有一种不带参数的版本,比如下面这个函数会根据输入的百分制成绩,返回对应的成绩等级。
如果使用 if 语句使用:
fun getGradeLevelByScore(score: Float) = if (score > 90) {
"A"
} else if (score > 80) {
"B"
} else if (score > 70) {
"C"
} else if (score > 60) {
"D"
} else {
"E"
}
如果使用不带参数的 when 语句来实现:
fun getGradeLevelByScore(score: Float): String = when {
score >= 90 -> "A"
score >= 80 -> "B"
score >= 70 -> "C"
score >= 60 -> "D"
else -> "E"
}
它是将每一个分支的条件判断都写在了 when 的内部。
循环语句
Java中主要有两种循环语句:while 循环和 for 循环,Kotlin 中也提供了 while 循环和 for 循环。while 循环的用法和 Java 基本相同,就不再赘述了。
接下来,来看看 Kotlin 中的 for 循环。
Kotlin 中并没有 Java 中的 for-i 循环,而是对 for-each 循环进行了改进,形成了for-in循环。
区间
Kotlin 加入了区间的概念,我们先来了解一下,比如下面这就是一个区间:
val range: IntRange = 0..10
这行代码创建出了一个 0 到 10 的区间,并且是闭区间,在数学中的表示就是 [0,10]。
.. 是创建两端闭区间的关键字,在 .. 的两边指定区间的端点。
for-in 遍历区间
有了一个区间后,就可以通过 for-in 循环来遍历它:
fun main() {
for (i in 0..10) {
println(i)
}
}
我们在 main() 函数中遍历了 [0,10] 这个区间中的每个元素,并将它们打印出来,运行结果:
很多时候,我们会使用单端区间,而不是双端区间。比如一个长度为 10 的数组,其下标的有效范围只是 0 到 9,我们不能也不需要访问下标为 10 的元素。
Kotlin 中使用 until 关键字就可以创建一个左闭右开的区间,比如:
val range: IntRange = 0 until 10
这行代码创建出了一个 0 到 10 的左闭右开区间,在数学中的表示就是 [0,10),并不包含 10。
循环遍历这个区间:
fun main() {
for (i in 0 until 10) {
println(i)
}
}
再次运行一下:
区间跳跃
默认情况下,for-in 循环在遍历区间时,每次循环都会跳到下一个元素,也就是步长为 1。如果我们想要跳过一些元素,我们可以使用 step 关键字修改步长。像这样:
fun main() {
for (i in 0 until 10 step 2) {
println(i)
}
}
运行结果:
降序区间
之前我们创建的都是升序的区间,如果想要一个降序区间,可以使用 downTo 关键字,像这样:
fun main() {
for (i in 10 downTo 1) {
println(i)
}
}
这里我们创建了一个[10, 1]的降序闭区间。
运行结果:
for-in 循环除了可以对区间进行遍历之外,还可以用于遍历数组和集合等可迭代对象。
总结一下:Kotlin 中的 for-in 循环虽然没有传统的 for-i 循环灵活,但足以应对大部分场景了,如果使用 for-in 循环难以实现需求,可以换成 while 循环。