从这篇文章开始,将会整理我在学习 Kotlin 中的一些笔记,进行知识的系统整理。主要受众是 Android 开发者,文中可能涉及一些和 Java 的语法对比,以及 Android 应用的一些例子。
在 Kotlin 中声明函数的方式和 Java 中有些不同,我们对比来看看。
//Java
public int add(int a, int b) {
return a + b;
}
复制代码
//Kotlin
fun add(a: Int, b: Int): Int {
return a + b
}
复制代码
这是一个比较简单的例子,总结一下:
fun <变量名>: <变量类型> : <返回值类型> ;
Kotlin 函数的其他语法特性:
当函数体只有一行时,可以使用 = 代替 return 和{} ,简化代码。
//表达式作为函数体,返回类型自动推断 fun add(a: Int, b: Int) = a + b 复制代码
当没有返回值时,Java 返回 void ,Kotlin 返回 Unit ,可以省略。
//Java
void print(){}
//Kotlin
fun print(): Unit{}
//省略Unit
fun print(){}
复制代码
一个完整的 Kotlin 函数声明格式如下:
<修饰符> fun <函数名>(<参数名>: <参数类型>, ...): <返回值类型> {
// 函数实现
}
public fun add(a: Int, b: Int): Int {
return a + b
}
复制代码
Kotlin 中不加任何修饰符,默认权限是 public ,在类中的函数等同于 Java 中的 public final ,不可被子类覆盖。
Kotlin 中的参数可以加上默认值,调用时省略这个变量时,使用默认值,可以 减少函数重载 。
// 以Android中弹toast为例
public fun showToast(text: String, time: Long = Toast.LENGTH_SHORT){
Toast.makeText(getApplicationContext(), text, time).show()
}
//相当于Java中两个重载函数
public void showToast(String text){
showToast(text, Toast.LENGTH_SHORT);
}
public void showToast(String text, Long time){
Toast.makeText(getApplicationContext(), text, time).show();
}
复制代码
有默认值的参数必须写明参数类型。
如果一个有默认值的参数在一个无默认值的参数之前,则不能省略有默认值的参数,或者使用命名参数。
当函数有大量参数时,传入参数时使用命名参数可以提升代码可读性。格式为: <变量名> = <变量值> 。
使用命名参数时,位置可以不用一一对应。
命名参数可以省略中间的有默认值的参数。
在传参时,如果有命名参数,后面的参数必须都是命名参数,否则编译不通过。
在 Kotlin 中可以使用 vararg 关键字来表示函数的可变长参数。
fun test(vararg list:String) {
for (str in list){
println(str)
}
}
fun main() {
test("one","two","three")
}
// 运行结果
one
two
three
复制代码
先举一个小例子,之后的文章中再具体介绍。
fun main(){
val sumLambda: (Int, Int) -> Int = {x, y -> x + y}
println(sumLambda(2, 3))
}
复制代码
Kotlin 中的变量可以分为 可变变量 和 不可变变量 ,分别用 var 和 val 进行修饰。
var 是 variable 的缩写 val 是 value 的缩写
var <变量名>: <类型> = <初始值>
只能赋值一次的变量,类似于Java中用final修饰的变量。
val <变量名>: <类型> = <初始值>
编译器支持自动类型推断,声明时可以不指定类型,编译器根据初始值自动推断类型。
在Kotlin中,变量默认均是不可为空的,例如在IDE中输入以下代码时:
var str: String = null 复制代码
编译器会提示报错,编译不通过。
Kotlin的空安全设计简单来说就是通过IDE的提示来避免调用null对象,从而避免NullPointerException。这样的空安全限制可以避免很多运行时的异常。
有的时候我们确实需要空值,这时候我们可以使用 可空类型 。在类型之后加一个 ? ,如 String? , View? 。
如上面的代码,应该修改为:
var str: String? = null 复制代码
不可空类型和可空类型是两个不同的类型。比如 String 和 String? 是不同的。
类型为 String? 的变量不可以直接赋值给类型为 String 的变量,因为 String?可以为 null,而 String 不可以,相当于缩小了范围,如果非要转换,需要在后面加 !! ,表示确定该变量一定不为 null。
反过来,类型为 String 的变量可以直接赋值给类型为 String? 的变量。
可空类型在调用时,不能直接使用 . 来调用变量的函数,而要使用 ?. 这样的 safe call 来调用,它其实也是先确认变量是否为空,在其非空的情况下才去调用函数,因为是语言级别的规则,它可以做到线程安全。普通的加上一个 if 判断,做不到线程安全,所以也不能编译通过。还可以使用 !! 来告诉编译器确认变量一定不为空,不用检查,这种调用称为 non-null asserted call ,其实就和 Java 中一样了,如果变量为 null,则会出现 NullPointerException。
var name: String = null
name.length() // 编译不通过
if(name != null){
name.length() // 编译不通过,无法保证多线程安全,可能存在其他线程将值改为null
}
name?.length() // safe call,编译通过,运行时这条语句不执行
name!!.length() //non-null asserted call,当前情况下,name为null,编译通过,但运行时会出现NullPointerException
复制代码
对于 Top-Level 的变量(直接定义在 kt 文件中的变量),必须在声明时指定初始值,或者使用 lateinit 关键字,来延迟初始化。
var p1 :String = "" //声明时指定初始值,不报错 var p2 : String //声明时没有指定初始值,报错 lateinit var p3:String // 使用lateinit关键字延迟初始化,不报错 复制代码
对于定义在类中的变量,必须在声明时指定初始值,或者声明成 abstract (类也要变成abstract)、 lateinit 。
abstract class ClassA {
var p1: String // 编译不通过,提示Property must be initialized or be abstract
abstract var p2: String //将变量声明为abstract,可以通过编译,类也要改为 abstract
lateinit var p3: String //使用lateinit关键字延迟初始化
}
复制代码
对于函数中的局部变量,可以在声明时不指定初始值,但是在使用之前必须要有赋值操作,否则也会编译不过。
fun main() {
var str: String //这里没有指定初始值,不报错
println(str) //在使用时没有初始化,编译不通过
}
//在使用前进行赋值操作
fun main() {
var str: String //这里没有指定初始值,不报错
str = "hello"
println(str) //编译通过
}
复制代码
lateinit 关键字用于解决在声明时变量,不能第一时间赋初始值的情况。(比如 Android 开发中,声明控件变量,必须要在 Activity 的 onCreate 的方法中进行初始化)它的作用是让 IDE 不要对这个变量检查初始化和报错,这个变量的初始化完全由开发者决定。lateinit 只能用来修饰 var,不能用来修饰 val。