参考官方文档:https://developer.android.google.cn/kotlin/learn?hl=zh-cn
1、变量声明
Kotlin 使用两个不同的关键字(即 val 和 var)来声明变量。
- val 用于值从不更改的变量。使用 val 声明的变量无法重新赋值。
- var 用于值可以更改的变量。
// 可变变量
var count: Int = 10
count = 15
// 不可变变量
val name = "kotlin"
name = "java" // 报错
2、类型推断
- Kotlin 是一种静态类型的语言。类型将在编译时解析且从不改变。
val languageName= "kotlin"
val upperCaseName = languageName.toUpperCase()
// Fails to compile
languageName.inc()
- toUpperCase() 是一个只能对 String 类型的变量调用的函数。由于 Kotlin 编译器已将 languageName 推断为 String,因此可以安全地调用 toUpperCase()。不过,inc() 是一个 Int 运算符函数,因此无法对 String 调用它。利用 Kotlin 的类型推断,既能确保代码简洁,又能确保类型安全。
3、Null 安全
- Kotlin 提供了严格的可为 null 性规则,可在您的整个应用中维护类型安全。在 Kotlin 中,默认情况下,对对象的引用不能包含 null 值。如需为变量赋 null 值,必须通过将变量类型以问号 ? 为后缀,声明变量为可 null 的类型。
// name 的类型为 String,不可为 null,会编译错误
val name: String = null
// 若要赋 null 值,需加 ? 符号
val name: String? = null
4、非 null 断言运算符 !!
5、条件语句
- Kotlin 提供了几种实现条件逻辑的机制,其中最常见的是 if-else 语句。如果 if 关键字后面括在圆括号内的表达式求值为 true,则会执行该分支中的代码(即,紧跟在后面的括在大括号内的代码)。否则,会执行 else 分支中的代码。
if (count == 42) {
println("I have the answer.")
} else {
println("The answer eludes me.")
}
- 您可以使用 else if 表示多个条件。这样,您就可以在单个条件语句中表示更精细、更复杂的逻辑。
if (count == 42) {
println("I have the answer.")
} else if (count > 35) {
println("The answer is close.")
} else {
println("The answer eludes me.")
}
- 为了避免重复代码,Kotlin 提供了条件表达式。每个条件分支都隐式地返回其最后一行的表达式的结果,因此无需使用 return 关键字
val answerString: String = if (count == 42) {
"I have the answer."
} else if (count > 35) {
"The answer is close."
} else {
"The answer eludes me."
}
println(answerString)
- 随着条件语句的复杂性不断增加,您可以考虑将 if-else 表达式替换为 when 表达式。when 表达式中每个分支都由一个条件、一个箭头 (->) 和一个结果来表示。如果箭头左侧的条件求值为 true,则会返回右侧的表达式结果。
val answerString = when {
count == 42 -> "I have the answer."
count > 35 -> "The answer is close."
else -> "The answer eludes me."
}
- Kotlin 的条件语句有一项强大的功能,即智能类型转换。您不必使用安全调用运算符或非 null 断言运算符来处理可为 null 的值,而可以使用条件语句来检查变量是否包含对 null 值的引用。
val languageName: String? = null
if (languageName != null) {
// No need to write languageName?.toUpperCase()
println(languageName.toUpperCase())
}
6、函数
- 使用 fun 关键字声明函数,后跟函数名称,接着在括号内定义函数的输入类型(如果有),最后使用冒号(:)跟着返回值的类型。在声明函数时,可以指定任意数量的参数及其类型。
fun getName(type: Int): String {
// 函数体内容
}
(1)简化函数声明
- 函数返回单个表达式的结果时,可以通过直接返回函数中包含的 if-else 表达式的结果来跳过声明局部变量。
fun generateAnswerString(countThreshold: Int): String {
return if (count > countThreshold) {
"I have the answer."
} else {
"The answer eludes me."
}
}
- 还可以将 return 关键字替换为赋值运算符。
fun generateAnswerString(countThreshold: Int): String = if (count > countThreshold) {
"I have the answer"
} else {
"The answer eludes me"
}
(2)匿名函数
- 并非每个函数都需要一个名称。某些函数通过输入和输出更直接地进行标识。这些函数称为“匿名函数”。可以保留对某个匿名函数的引用,以便日后使用此引用来调用该匿名函数。与其他引用类型一样,也可以在应用中传递引用。必须像调用命名函数一样调用该函数。
val stringLengthFunc: (String) -> Int = { input ->
input.length
}
val stringLength: Int = stringLengthFunc("Android")
(3)高阶函数
- 一个函数可以将另一个函数当作参数。将其他函数用作参数的函数称为“高阶函数”。此模式对组件之间的通信(其方式与在 Java 中使用回调接口相同)很有用。
fun stringMapper(str: String, mapper: (String) -> Int): Int {
// Invoke function
return mapper(str)
}
- 如果匿名函数是在某个函数上定义的最后一个参数,则可以在用于调用该函数的圆括号之外传递它。
stringMapper("Android") { input ->
input.length
}
7、类
使用 class 关键字可以定义类。
(1)属性
- 类使用属性来表示状态。属性是类级变量,可以包含 getter、setter 和后备字段。
class Car {
val wheels = listOf<Wheel>()
}
- 调用类默认构造函数可以获取类的实例,通过实例可以访问它的任何可访问属性。
val car = Car() // construct a Car
val wheels = car.wheels // retrieve the wheels value from the Car
- 可以定义一个自定义构造函数,用来指定如何初始化类属性。
class Car(val wheels: List<Wheel>)
(2)类函数和封装
- 类使用函数对行为建模。函数可以修改状态,从而实现只公开希望公开的数据。这种访问控制机制称为“封装”。
class Car(val wheels: List<Wheel>) {
private val doorLock: DoorLock = ...
fun unlockDoor(key: Key): Boolean {
// Return true if key is valid for door lock, false otherwise
}
}
- 如果希望自定义属性的引用方式,则可以提供自定义的 getter 和 setter。例如,如果希望公开属性的 getter 而限制访问其 setter,则可以将该 setter 指定为 private:
class Car(val wheels: List<Wheel>) {
private val doorLock: DoorLock = ...
var gallonsOfFuelInTank: Int = 15
private set
fun unlockDoor(key: Key): Boolean {
// Return true if key is valid for door lock, false otherwise
}
}
8、互操作性
- Kotlin 最重要的功能之一就是它与 Java 之间流畅的互操作性。由于 Kotlin 代码可编译为 JVM 字节码,因此 Kotlin 代码可直接调用 Java 代码,反之亦然。这意味着,可以直接从 Kotlin 利用现有的 Java 库。此外,绝大多数 Android API 都是用 Java 编写的,因此可以直接从 Kotlin 调用它们。
- 可为 null 性是 Java 和 Kotlin 在行为上有所不同的一个主要方面。Java 对可为 null 性语法的要求不那么严格。Kotlin 制定了与可为 null 性有关的规则,Java 没有制定这样的规则,而是依赖于可选的可为 null 性注释明确声明您是否可以赋予 null 值。
private final @Nullable String accessId;
public final @NonNull String name;
public final String loginName;
- 如果使用 Kotlin 引用在 Java 中定义的不带注释的 loginName 成员,编译器将不知道 String 映射到 Kotlin 中的 String 还是 String?。这种不明确性通过平台类型 String! 表示。
- String! 对 Kotlin 编译器而言没有特殊的含义。String! 可以表示 String 或 String?,编译器可让您赋予任一类型的值。请注意,如果您将类型表示为 String 并赋予 null 值,则系统可能会抛出 NullPointerException。
- 为了解决此问题,每当您用 Java 编写代码时,都应使用可为 null 性注释。这些注释对 Java 和 Kotlin 开发者都有帮助。
- 可为 null 性注释包含在所有新增的 Android API 以及许多现有的 Android API 中。许多 Java 库已添加可为 null 性注释,以便为 Kotlin 和 Java 开发者提供更好的支持。
9、伴生对象(Companion Object)
- 伴生对象(Companion Object) 是一种特殊的对象,它与类相关联,但不属于类的某个具体实例。伴生对象类似于 Java 中的静态成员。
- 伴生对象是通过在类内部使用 companion object 关键字定义的。它允许你在类内部定义一些静态方法或属性,这些方法和属性可以通过类名直接访问,而不需要创建类的实例。
- 伴生对象的成员在编译后会被转换为 Java 的静态成员,因此在 Java 代码中可以直接访问它们。
- 伴生对象常用于实现工厂模式,提供类的创建方法。
class LoginFragment : Fragment() {
...
companion object {
private const val TAG = "LoginFragment"
}
}
10、属性委托
- 初始化属性时,您可能会重复 Android 的一些比较常见的模式,例如在 Fragment 中访问 ViewModel。为避免过多的重复代码,您可以使用 Kotlin 的属性委托语法。
- 属性委托提供了一种可在您的整个应用中重复使用的通用实现。Android KTX 提供了一些属性委托。例如,viewModels 可检索范围限定为当前 Fragment 的 ViewModel。
- 属性委托使用反射,这样会增加一些性能开销。这种代价换来的是简洁的语法,可让您节省开发时间。
private val viewModel: LoginViewModel by viewModels()