写在前面
Andorid第一行代码Kotlin小课堂
变量和函数
变量
Kotlin中定义一个变量,只允许在变量前声明两种关键字:val和var
- val(value的简写的简写)用来声明一个不可变的变量,这种变量在初始赋值之后就再也不能重新赋值,对应Java中的final变量。
- var(variable的简写的简写)用来声明一个可变的变量,这种变量在初始赋值之后仍然可以再被重新赋值复制,对应Java中的非final变量。
Kotlin完全抛弃了Java中的基本数据类型,全部使用了对象数据类型。在Java中int是整型变量的关键字,而在Kotlin中Int变成了一个类,它拥有自己的方法和继承结构。
函数
定义一个函数的语法规则如下:
1
2
3
fun methodName(param1: Int, param2: Int): Int {
return 0
}
fun(function的简写)是定义函数的关键字,无论你定义什么函数,都一定要使用fun来声明。
紧跟在fun后面的是函数名,良好的编程习惯是函数名最好要有一定的意义,能表达这个函数的作用是什么。
函数名后面的一对括号中,可以声明该函数接收什么参数。
括号后面的部分是可选的,用于声明该函数会返回什么类型的数据。如果不需要返回任何数据,这部分可以不写。
两个大括号之间的内容就是函数体了,可以在这里编写函数的具体逻辑。
当一个函数的函数体中只有一行代码时,可以使用单行代码函数的语法糖:
1
fun methodName(param1: Int, param2: Int) = 0
使用这种写法,可以直接将唯一的一行代码写在函数定义的尾部,中间用等号连接即可。
return关键字也可以省略,等号足以表达返回值的意思。
Kotlin还拥有出色的类型推导机制,可以自动推导出返回值的类型。
逻辑控制
if条件语句
Kotlin中的if语句相比于Java有一个额外的功能:它是可以有返回值的,返回值就是if语句每一个条件中最后一行代码的返回值。
1
2
3
4
5
6
7
8fun largerNumber(num1: Int, num2: Int): Int {
val value = if (num1 > num2) {
num1
} else {
num2
}
return value
}仔细观察上述代码,你会发现value其实是一个多余的变量,我们可以直接将if语句返回,这样代码将会变得更加精简,如下所示:
1
2
3
4
5
6
7fun largerNumber(num1: Int, num2: Int): Int {
return if (num1 > num2) {
num1
} else {
num2
}
}当一个函数只有一行代码时,可以省略函数体部分,直接将这一行代码使用等号串连在函数定义的尾部。虽然largerNumber()函数不止只有一行代码,但是它和只有一行代码的作用是相同的,只是return了一下if语句的返回值而已,符合该语法糖的使用条件。那么我们就可以将代码进一步精简:
1
2
3
4
5fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) {
num1
} else {
num2
}最后,还可以将上述代码再精简一下,直接压缩成一行代码:
1
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2
when条件语句
- 当需要判断的条件非常多的时候,可以考虑使用when语句来替代if语句。
1
2
3
4
5
6
7fun getScore(name: String) = when (name) {
"Tom" -> 86
"Jim" -> 77
"Jack" -> 95
"Lily" -> 100
else -> 0
} - 除了精确匹配之外,when语句还允许进行类型匹配。
1
2
3
4
5
6
7fun checkNumber(num: Number) {
when (num) {
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
}
for-in循环语句
我们可以使用如下Kotlin代码来表示一个区间:
1
val range = 0..10
上述代码表示创建了一个0到10的区间,并且两端都是闭区间,这意味着0到10这两个端点都是包含在区间中的,用数学的方式表达出来就是[0, 10]。也可以使用until关键字来创建一个左闭右开的区间:
1
val range = 0 until 10
上述代码表示创建了一个0到10的左闭右开区间,它的数学表达方式是[0, 10)。有了区间之后,我们就可以通过for-in循环来遍历这个区间:
1
2
3
4
5fun main() {
for (i in 0..10) {
println(i)
}
}如果你想跳过其中的一些元素,可以使用step关键字:
1
2
3
4
5fun main() {
for (i in 0 until 10 step 2) {
println(i)
}
}如果你想创建一个降序的区间,可以使用downTo关键字:
1
2
3
4
5fun main() {
for (i in 10 downTo 1) {
println(i)
}
}
面向对象编程
类与对象
可以使用如下代码定义一个类,以及声明它所拥有的字段和函数:
1
2
3
4
5
6
7
8class Person {
var name = ""
var age = 0
fun eat() {
println(name + " is eating. He is " + age + " years old.")
}
}然后使用如下代码创建对象,并对对象进行操作:
1
2
3
4
5
6fun main() {
val p = Person()
p.name = "Jack"
p.age = 19
p.eat()
}
继承
Kotlin中一个类默认是不可以被继承的,如果想要让一个类可以被继承,需要主动声明open关键字:
1
2
3open class Person {
…
}要让另一个类去继承Person类,则需要使用冒号关键字:
1
2
3
4class Student : Person() {
var sno = ""
var grade = 0
}现在Student类中就自动拥有了Person类中的字段和函数,还可以定义自己独有的字段和函数。
接口
- Kotlin中定义接口的关键字和Java中是相同的,都是使用的interface:
1
2
3
4interface Study {
fun readBooks()
fun doHomework()
} - 而Kotlin中实现接口的关键字变量了冒号,和继承使用的是同样的关键字:
1
2
3
4
5
6
7
8
9class Student(val name: String, val age: Int) : Study {
override fun readBooks() {
println(name + " is reading.")
}
override fun doHomework() {
println(name + " is doing homework.")
}
}
数据类
- Kotlin中使用data关键字可以定义一个数据类: Kotlin会根据数据类的主构造函数中的参数将equals()、hashCode()、toString()等固定且无实际逻辑意义的方法自动生成,从而大大简少了开发的工作量。
1
data class Cellphone(val brand: String, val price: Double)
单例类
Kotlin中使用object关键字可以定义一个单例类:
1
2
3
4
5object Singleton {
fun singletonTest() {
println("singletonTest is called.")
}
}而调用单例类中的函数比较类似于Java中静态方法的调用方式:
1
Singleton.singletonTest()
这种写法虽然看上去像是静态方法的调用,但其实Kotlin在背后自动帮我们创建了一个Singleton类的实例,并且保证全局只会存在一个Singleton实例。
Lambda编程
集合的创建
使用如下代码可以初始化一个List集合:
1
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
使用如下代码可以初始化一个Set集合:
1
val set = setOf("Apple", "Banana", "Orange", "Pear", "Grape")
使用如下代码可以初始化一个Map集合:
1
val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
Lambda表达式
Lambda就是一小段可以作为参数传递的代码。正常情况下,我们向某个函数传参时只能传入变量,而借助Lambda却允许传入一小段代码。
我们来看一下Lambda表达式的语法结构:
{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}
首先最外层是一对大括号,如果有参数传入到Lambda表达式中的话,我们还需要声明参数列表,参数列表的结尾使用一个->符号,表示参数列表的结束以及函数体的开始,函数体中可以编写任意行代码,并且最后一行代码会自动作为Lambda表达式的返回值。
集合的函数式API
集合中的map函数是最常用的一种函数式API,它用于将集合中的每个元素都映射成一个另外的值,映射的规则在Lambda表达式中指定,最终生成一个新的集合。比如,这里我们希望让所有的水果名都变成大写模式,就可以这样写:
1
2
3
4
5
6
7
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.map({ fruit: String -> fruit.toUpperCase() })
for (fruit in newList) {
println(fruit)
}
}
当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到函数括号的外面。
如果Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略。
由于Kotlin拥有出色的类型推导机制,Lambda表达式中的参数列表其实在大多数情况下也不必声明参数类型。
当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字来代替。
因此,Lambda表达式的写法可以进一步简化成如下方式:
1
val newList = list.map { it.toUpperCase() }
Java函数式API
如果我们在Kotlin代码中调用了一个Java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API。Java单抽象方法接口指的是接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数式API。
举个例子,Android中有一个极为常用的点击事件接口OnClickListener,其定义如下:
1
2
3
public interface OnClickListener {
void onClick(View v);
}
可以看到,这是一个单抽象方法接口。假设现在我们拥有一个按钮button的实例,就可以使用函数式API的写法来注册这个按钮的点击事件:
1
2
button.setOnClickListener { v ->
}
空指针检查
- 空指针是一种不受编程语言检查的运行时异常,只能由程序员主动通过逻辑判断来避免,但即使是最出色的程序员,也不可能将所有潜在的空指针异常全部考虑到。 这段Java代码安全吗?不一定,因为这要取决于调用方传入的参数是什么,如果我们向doStudy()方法传入了一个null参数,那么毫无疑问这里就会发生空指针异常。因此,更加稳妥的做法是在调用参数的方法之前先进行一个判空处理,如下所示:
1
2
3
4public void doStudy(Study study) {
study.readBooks();
study.doHomework();
}1
2
3
4
5
6public void doStudy(Study study) {
if (study != null) {
study.readBooks();
study.doHomework();
}
}
可空类型系统
Kotlin中引入了一个可空类型系统的概念,它利用编译时判空检查的机制几乎杜绝了空指针异常。
1
2
3
4fun doStudy(study: Study) {
study.readBooks()
study.doHomework()
}这段代码看上去和刚才的Java版本并没有什么区别,但实际上它是没有空指针风险的,因为Kotlin默认所有的参数和变量都不可为空,所以这里传入的Study参数也一定不会为空,可以放心地调用它的任何函数。
Kotlin提供了另外一套可为空的类型系统,就是在类名的后面加上一个问号。比如,Int表示不可为空的整型,而Int?就表示可为空的整型;String表示不可为空的字符串,而String?就表示可为空的字符串。
使用可为空的类型系统时,需要在编译时期就把所有的空指针异常都处理掉才行。
判空辅助工具
Kotlin提供了一系列的辅助工具,使开发者能够更轻松地进行判空处理。
?. 操作符表示当对象不为空时正常调用相应的方法,当对象为空时则什么都不做。比如:
1
2
3if (a != null) {
a.doSomething()
}这段代码使用?.操作符就可以简化成:
1
a?.doSomething()
?: 操作符表示如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果。比如:
1
2
3
4
5val c = if (a ! = null) {
a
} else {
b
}这段代码的逻辑使用?:操作符就可以简化成:
1
val c = a ?: b
结合使用?.操作符和let函数也可以对多次重复调用的某个变量统一进行判空处理:
1
2
3
4
5
6fun doStudy(study: Study?) {
study?.let {
it.readBooks()
it.doHomework()
}
}
Kotlin中的小魔术
字符串内嵌表达式
在Kotlin中,我们可以直接将表达式写在字符串里面,即使是构建非常复杂的字符串,也会变得轻而易举。
Kotlin中字符串内嵌表达式的语法规则如下:
1
"hello, ${obj.name}. nice to meet you!"
当表达式中仅有一个变量的时候,还可以将两边的大括号省略:
1
"hello, $name. nice to meet you!"
函数的参数默认值
Kotlin允许在定义函数的时候给任意参数设定一个默认值,这样当调用此函数时就不会强制要求调用方为此参数传值,在没有传值的情况下会自动使用参数的默认值。语法格式如下:
1
2
3fun printParams(num: Int, str: String = "hello") {
println("num is $num , str is $str")
}这里给printParams()函数的第二个参数设定了一个默认值,这样当调用printParams()函数时,可以选择给第二个参数传值,也可以选择不传,在不传的情况下就会自动使用默认值。
- 本文链接: https://blog.hansong.icu/2020/12/12/kotlin/
- 版权声明: 本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。