前言
Kotlin 是一种现代化的静态类型编程语言,最初由 JetBrains 开发并于 2011 年公开发布。它是一种通用编程语言,设计用于与 Java 100% 兼容,因此可以在 Java 平台上无缝运行。Kotlin 专注于简洁性、可读性和可维护性,并且旨在减少代码的样板和冗余性。
Kotlin 具有许多吸引人的特性,包括:
与 Java 互操作性: Kotlin 可以与 Java 代码完全互操作,这意味着现有的 Java 项目可以逐步采用 Kotlin,而不需要重新编写整个代码库。
空安全: Kotlin 在语言级别支持空安全,这意味着它能够防止常见的空指针异常,并提供更安全的代码。
扩展函数: Kotlin 允许开发者在不修改类定义的情况下向现有类添加新方法,这通过所谓的扩展函数实现。
函数式编程支持: Kotlin 提供了许多函数式编程的特性,如 Lambda 表达式、高阶函数等,使得编写函数式风格的代码更加容易。
协程: Kotlin 通过协程提供了一种轻量级的并发编程方式,能够简化异步代码的编写,提高代码的可读性和可维护性。
数据类: Kotlin 提供了数据类(data class)的概念,简化了用于数据传输和持久化的类的编写。
可扩展性: Kotlin 允许开发者通过编写自定义的 DSL(领域特定语言)来扩展语言的功能,从而使其适应特定领域的需求。
总的来说,Kotlin 是一种强大而现代的编程语言,适用于各种应用场景,从 Android 移动应用开发到企业级后端服务开发。
其简洁性、安全性和与 Java 的无缝集成使得它成为了许多开发者的首选之一。
那么性能呢?
Kotlin 和 Java 在性能方面没有明显的优劣之分,它们在 JVM 上都可以实现高性能的应用程序。
这是因为 Kotlin 与 Java 共享 JVM 运行环境,并且 Kotlin 编译器会将 Kotlin 代码编译成与 Java 字节码兼容的字节码。
数据类型
在 Kotlin 中,数据类型包括基本数据类型和引用数据类型。下面是 Kotlin 中常见的数据类型:
基本数据类型:
- 整数类型:
Byte
:8 位有符号整数,取值范围为 -128 到 127。Short
:16 位有符号整数,取值范围为 -32768 到 32767。Int
:32 位有符号整数,取值范围为 -2^31 到 2^31 - 1。Long
:64 位有符号整数,取值范围为 -2^63 到 2^63 - 1。
- 浮点类型:
Float
:32 位单精度浮点数。Double
:64 位双精度浮点数。
- 其他基本类型:
Char
:16 位 Unicode 字符。Boolean
:布尔类型,取值为true
或false
。
引用数据类型:
- 字符串:使用
String
类表示,是不可变的。 - 数组:Kotlin 中的数组有固定大小,使用
Array
类表示。 - 集合:Kotlin 标准库提供了丰富的集合类,如
List
、Set
、Map
等。 - 类:开发者可以定义自己的类来表示复杂的数据结构。
Kotlin 还支持类型推断,这意味着在大多数情况下,编译器可以推断出变量的类型,而不需要显式地指定类型。
例如:
1 | val number = 42 // Kotlin 编译器会推断出 number 的类型为 Int |
字符串
下面的 Kotlin 代码示例,展示了如何使用字符串模板:
1 | fun main() { |
在上面的示例中,我们定义了变量 name
、age
和 city
,然后使用字符串模板将它们嵌入到字符串中。
在第二个 println
语句中,我们还展示了如何在字符串模板中包含表达式。
最后,我们调用了 greet
函数,并将字符串模板作为参数传递给函数。
数组操作
在Kotlin中,数组的操作可以通过标准库中的Array
类来完成。
以下是一些常见的 Kotlin 数组操作:
创建数组
使用构造函数创建数组:
1 | val array = Array(5) { i -> i * 2 } // 创建一个包含 5 个元素的数组,每个元素的值是索引的两倍 |
使用arrayOf
函数创建数组:
1 | val array = arrayOf(1, 2, 3, 4, 5) // 创建一个包含指定元素的数组 |
访问数组元素
1 | val element = array[index] // 获取数组中指定索引处的元素值 |
修改数组元素
1 | array[index] = value // 修改数组中指定索引处的元素值 |
数组切片
1 | val subArray = array.sliceArray(1..3) // 返回数组中指定范围的子数组 |
数组合并
1 | val combinedArray = array1 + array2 // 将两个数组合并为一个新数组 |
遍历数组
使用for
循环:
1 | for (element in array) { |
使用forEach
高阶函数:
1 | array.forEach { element -> |
数组转换
1 | val newArray = array.map { it * 2 } // 将数组中的每个元素乘以 2 并返回一个新的数组 |
过滤数组
1 | val filteredArray = array.filter { it % 2 == 0 } // 过滤数组中的偶数元素 |
查找数组元素
1 | val index = array.indexOf(element) // 返回指定元素在数组中的索引,如果不存在则返回 -1 |
数组排序
1 | val sortedArray = array.sorted() // 返回一个排序后的新数组 |
集合类
Set/List/Map
首先要说的是没有new
了
集合的分类:
- Set(集)
- List(列表)
- Map(映射)
在Kotlin
中,明确的区分了只读
和可变
的集合
代码如下 前三个是只读 后三个是可变的
1 | var list = listOf<String>("aa", "bb", "cc") |
可变长度
1 | var mList = mutableListOf<String>(); |
List处理
遍历
只遍历项
1 | var list = listOf<String>("aa", "bb", "cc") |
遍历索引和项
有几种方法可以做到这一点,以下是其中的一些:
使用 forEachIndexed
函数:
1 | val list = listOf("a", "b", "c") |
使用 indices
和 get
属性:1
2
3
4val list = listOf("a", "b", "c")
for (i in list.indices) {
println("索引 $i 对应的项为 ${list[i]}")
}
使用 withIndex()
函数:1
2
3
4val list = listOf("a", "b", "c")
for ((index, item) in list.withIndex()) {
println("索引 $index 对应的项为 $item")
}
以上方法都可以用来遍历 List,并在每次迭代中获取索引和项。
选择哪种方法取决于个人偏好和代码风格。
map
1 | val ulist = mutableListOf<String>("A", "B", "C") |
结果
ulistNew:[a, b, c]
filter
1 | val ulist = mutableListOf<Int>(1, 2, 3) |
结果
ulistNew:[2, 3]
foreach
1 | val ulist = mutableListOf<Int>(1, 2, 3) |
结果
1
2
3
可变长参数函数
函数的变长参数可以用 vararg 关键字进行标识
1 | fun varargFun(vararg v:String){ |
类
数据类(pojo)
1 | data class Customer(val name: String, val email: String) |
自动添加的方法:
- 为所有属性添加
getters
,如果为var
类型同时添加setters
- equals()
- haseCode()
- toString()
- copy()
数据类以及解构声明
组件函数允许数据类在解构声明中使用:
1 | val jane = User("Jane", 35) |
枚举
1 | enum class Color(val rgb: Int) { |
可见修饰符
- private:同一类或文件(针对包级别定义)中可见
- protected:同private 加子类可见
- internal:在同一个模块中可见(如果声明范围的所有者是可见的)
- public:公共,所有都可见
单例
1 | object MyClass{ |
那么MyClass.aa()
其实就是调用单例的aa方法
继承
open
关键字用于声明一个可以被继承或者被子类重写的类、方法或者属性。
Kotlin 中的类、方法、属性默认是不可继承或重写的,如果你希望某个类可以被其他类继承,或者某个方法或属性可以在子类中被重新实现,就需要使用 open
关键字来进行声明。
自定义组件
1 | open class SpliterLinearLayout : LinearLayoutCompat { |
类扩展
1 | fun <T> MutableList<T>.swap(x: Int, y: Int) { |
调用方式
1 | val list = mutableListOf(1, 2, 3) |
扩展是被
静态解析
的
延迟初始化
在 Kotlin 中,lateinit
关键字用于延迟初始化属性,即在声明属性时不需要立即赋初始值,而是在后续的某个时间点再进行赋值。
lateinit
只能用于非空类型的属性,并且必须是类属性(不能是局部变量)。
下面是一个简单的示例,演示了如何在 Kotlin 中使用 lateinit
:
1 | class Example { |
在这个示例中,name
属性被标记为 lateinit
,在 initializeName()
方法中对其进行了初始化,然后在 printName()
方法中检查是否已经被初始化并打印其值。
需要注意的是,如果在访问一个未被初始化的 lateinit
属性时,会抛出 UninitializedPropertyAccessException
异常,因此在访问之前必顋确保已经对其进行了初始化。
类对比
Java
1 | Intent t = new Intent(); |
Kotlin
1 | val t = Intent() |
接口写法对比
Java
1 | OkGo.<TUserBean>post("") |
Kotlin
1 | OkGo.post<TUserBean>("") |
静态属性和方法
Kotlin中定义
1 | class ApiModel { |
Kotlin中调用
1 | ApiModel.baseUrl |
Java中调用
1 | ApiModel.Companion.getBaseUrl(); |
所以建议静态属性和方法建议还用
Java
来写
类转换
1 | activity as ClassActivity |
表达式
三目运算
1 | if(a>b) a else b |
When 表达式
when 取代了 C 风格语言的 switch
也能替换if/else
1 | when (x) { |
循环
1 | for (i in 1..4) print(i) //打印1234 |
对象内置函数
大括号括着代表这个函数结合了前面函数的功能。
let函数
不为空后执行
1 | val date = ... |
also函数
作用 & 应用场景
类似let函数,但区别在于返回值:
- let函数:返回值 = 最后一行 / return的表达式
- also函数:返回值 = 传入的对象的本身
使用示例
1 | // let函数 |
with函数
作用
调用同一个对象的多个方法 / 属性时,可以省去对象名重复,直接调用方法名 / 属性即可
应用场景
需要调用同一个对象的多个方法 / 属性
使用方法
1 | with(object){ |
使用示例
1 | // 此处要调用people的name 和 age属性 |
run函数
作用
结合了let、with两个函数的作用,即:
- 调用同一个对象的多个方法 / 属性时,可以省去对象名重复,直接调用方法名 / 属性即可
- 定义一个变量在特定作用域内
- 统一做判空处理
使用方法
1 | object.run{ |
使用示例
1 | // 此处要调用people的name 和 age属性,且要判空 |
apply函数
作用 & 应用场景
与run函数类似,但区别在于返回值:
- run函数返回最后一行的值 / 表达式
- apply函数返回传入的对象的本身
应用场景
对象实例初始化时需要对对象中的属性进行赋值 & 返回该对象
使用示例
1 | // run函数 |
函数
传参写在大括号中
在 Kotlin 中,如果函数的最后一个参数是一个函数,那么可以将这个函数参数写在大括号 {}
中,这样在调用该函数时可以更清晰地表达函数的意图。
这种写法通常用于函数式编程的场景,例如定义高阶函数或使用函数字面值(lambda 表达式)。
下面是一个简单的示例,展示了如何在 Kotlin 中使用大括号 {}
来传递函数参数:
1 | fun process(text: String, block: (String) -> Unit) { |
在上面的例子中,process
函数的最后一个参数是一个函数 (String) -> Unit
,通过将这个函数写在大括号 {}
中,可以更清晰地看到这个函数是作为一个代码块传递给了 process
函数。
省略小括号
在 Kotlin 中,函数调用可以省略括号的情况有以下两种:
无参函数调用:
当函数没有参数时,可以省略函数调用时的括号。
这通常用于无副作用的函数,例如 Kotlin 标准库中的一些扩展函数,比如 isEmpty()
。
示例如下:
1 | val list = listOf(1, 2, 3) |
Lambda 表达式是函数的最后一个参数:
当函数的最后一个参数是一个 Lambda 表达式时,可以将 Lambda 表达式移到函数调用的外部,且省略函数调用时的括号。
这种语法被称为尾随闭包(Trailing Closures)。
示例如下:
1 | val numbers = listOf(1, 2, 3, 4, 5) |
在上述示例中,map
函数的最后一个参数是一个 Lambda 表达式,因此可以省略函数调用时的括号,并将 Lambda 表达式移到函数调用的外部。
异常处理
在 Kotlin 中,异常处理与 Java 类似,但有一些语法上的差异和特点。
下面是 Kotlin 中异常处理的一般方法:
try-catch 块:
Kotlin 使用 try
和 catch
关键字来捕获异常。
与 Java 不同的是,在 Kotlin 中,catch
子句被看作是一个表达式,因此它可以返回一个值。这使得异常处理更加灵活。
示例如下:
1 | try { |
try 表达式:
Kotlin 还提供了 try
表达式,它类似于 Java 中的 try-catch-finally
结构,但它可以作为表达式来使用,因此可以直接返回一个值。
示例如下:
1 | val result = try { |
Throw 表达式:
在 Kotlin 中,可以使用 throw
关键字抛出异常。与 Java 类似,你可以抛出任何类型的异常。示例如下:
1 | fun divide(a: Int, b: Int): Int { |
自定义异常类:
与 Java 一样,你可以在 Kotlin 中创建自定义异常类。示例如下:
1 | class CustomException(message: String) : Exception(message) |
不检查异常(Unchecked Exceptions):
Kotlin 不区分受检查异常和不受检查异常,因此不需要在函数声明中声明可能抛出的异常,但你仍然可以选择捕获或抛出异常。
总的来说,Kotlin 的异常处理与 Java 相似,但具有一些语法上的差异和特点,使得异常处理更加灵活和方便。
日期处理
以下是一个使用Kotlin处理日期的示例:
1 | import java.time.LocalDate |
这个示例演示了如何获取当前日期,格式化日期,解析字符串为日期,比较日期,以及对日期进行简单的算术操作,如添加或减去天数和月份。