Kotlin 学习
Kotlin 学习
基本语法
变量与常量
变量使用var,常量使用val
1  | fun main() {  | 
字符串模版
字符串模板使用 ${} 语法
1  | val a = 10  | 
多行文本字符串
1  | val multiLineString = """  | 
数组
原生类型数组
1  | // 创建一个大小为5的整数数组  | 
对象数组
1  | // 创建一个字符串数组  | 
常见操作
- 访问数组元素
 
1  | val value = intArray[2] // 获取下标为2的元素值  | 
- 遍历数组
 
1  | for (element in intArray) {  | 
- 使用数组的高阶函数
 
1  | // 使用forEach遍历数组  | 
- 数组大小
 
1  | val size = intArray.size // 获取数组大小  | 
集合
不可变集合
List
: 有序集合,允许重复元素。1
val list: List<String> = listOf("apple", "banana", "orange")
Set
: 无序集合,不允许重复元素。1
val set: Set<Int> = setOf(1, 2, 3, 4, 5)
Map<K, V>: 键值对集合。
1
val map: Map<String, Int> = mapOf("one" to 1, "two" to 2, "three" to 3)
可变集合
MutableList
: 可变的有序集合。1
2val mutableList: MutableList<String> = mutableListOf("apple", "banana", "orange")
mutableList.add("grape") // 添加元素MutableSet
: 可变的无序集合。1
2val mutableSet: MutableSet<Int> = mutableSetOf(1, 2, 3, 4, 5)
mutableSet.add(6) // 添加元素MutableMap<K, V>: 可变的键值对集合。
使用 to 来定义键值对
1
2val mutableMap: MutableMap<String, Int> = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
mutableMap["four"] = 4 // 添加键值对
常见操作
1、遍历集合
1  | for (element in list) {  | 
2、使用高阶函数
1  | // 使用forEach遍历集合  | 
1  | val filteredList = list.filter { it.startsWith("a") }  | 
4、转换为其他类型
1  | val setFromList = list.toSet()  | 
空安全
- 可空类型(Nullable Types)
 
在 Kotlin 中,使用 ? 符号来表示一个变量可以为 null。例如:
1  | val nullableString: String? = null  | 
- 安全调用运算符(Safe Call Operator)
 
使用 ?. 运算符来调用一个可空类型的方法或属性,如果对象为 null,表达式会返回 null 而不是抛出异常。
1  | val length: Int? = nullableString?.length  | 
- Elvis 运算符
 
Elvis 运算符 ?: 用于在表达式为 null 时提供一个备用值:
1  | val result: String = nullableString ?: "Default Value"  | 
- 非空断言运算符(Non-null Assertion Operator)
 
!! 运算符用于将一个可空类型强制转换为非空类型,如果对象为 null,则会抛出 NullPointerException。
1  | val length: Int = nullableString!!.length  | 
注意: 非空断言运算符应该谨慎使用,因为它会绕过 Kotlin 的空安全检查。
- 安全转换
 
使用 as? 运算符进行安全类型转换,如果转换失败,返回 null。
1  | val intValue: Int? = stringValue as? Int  | 
- lateinit 属性
 
lateinit 关键字用于标记属性为延迟初始化,允许在之后初始化,但在初始化之前访问该属性会抛出异常。
不推荐使用
1  | lateinit var name: String  | 
- 安全的类型检查和自动类型转换
 
使用 is 运算符进行安全的类型检查,并自动转换为非空类型:
1  | if (nullableString is String) {  | 
条件判断语句
在 Kotlin 中,条件判断语句主要有 if 表达式和 when 表达式。下面我将介绍这两种条件判断语句的用法:
if表达式
基本用法
1  | val number = 10  | 
作为表达式
if 语句在 Kotlin 中是一个表达式,可以用于赋值:
1  | val result = if (number > 0) "Positive" else "Non-positive"  | 
when表达式
基本用法
1  | val x = 5  | 
使用表达式作为分支条件
1  | val result = when (x) {  | 
使用 is 关键字进行类型检查
1  | val value: Any = "Hello"  | 
没有参数的 when 表达式
如果 when 表达式没有参数,它的分支条件是布尔表达式:
1  | val isEven = when {  | 
in 和 区间
区间表示一个连续的数值范围。在 Kotlin 中,区间可以表示为使用 .. 运算符的形式。
半开区间
1  | val range = 1..5 // [1, 2, 3, 4, 5]  | 
开区间
1  | val openRange = 1 until 5 // [1, 2, 3, 4]  | 
自定义步长的区间
1  | val customRange = 10 downTo 1 step 2 // [10, 8, 6, 4, 2]  | 
循环控制
在 Kotlin 中,有两种主要的循环结构:for 循环和 while 循环。此外,还有一种 do-while 循环。以下是这些循环结构的基本用法:
for循环
遍历集合或数组
1  | val numbers = listOf(1, 2, 3, 4, 5)  | 
使用 .. 创建数值范围
1  | for (i in 1..5) {  | 
使用 downTo 和 step 创建递减和自定义步长的范围
1  | for (i in 5 downTo 1 step 2) {  | 
while循环
基本用法
1  | var x = 0  | 
do-while循环
基本用法
1  | var y = 0  | 
foreach循环- 控制流语句
 
在 Kotlin 中,forEach 是集合类(包括数组)提供的一个扩展函数,用于遍历集合中的元素。这是一种更具 Kotlin 风格的循环语法。以下是 forEach 的用法:
1  | val numbers = listOf(1, 2, 3, 4, 5)  | 
在上面的例子中,forEach 接受一个 lambda 表达式作为参数,该 lambda 表达式会对集合中的每个元素执行相应的操作。在 lambda 表达式中,number 是集合中的元素。
对于数组,forEach 同样适用:
1  | val numbersArray = intArrayOf(1, 2, 3, 4, 5)  | 
forEach 使得集合的遍历更加简洁和易读,是 Kotlin 中推荐的一种方式。需要注意的是,forEach 是只读的,不能用于修改集合中的元素。如果需要修改元素,可以使用 forEachIndexed,该函数提供了元素索引的信息:
1  | val mutableList = mutableListOf(1, 2, 3, 4, 5)  | 
这样,forEach 可以方便地遍历集合中的元素,而 forEachIndexed 则提供了对元素索引的访问。
break 和 continue
在循环中使用 break 可以退出循环,而 continue 可以跳过当前迭代。
1  | for (i in 1..10) {  | 
return 标签
在嵌套循环中,可以使用 return 标签从最外层的函数或 lambda 表达式中返回。
1  | fun foo() {  | 
函数
在 Kotlin 中,函数是一等公民,具有许多强大的特性。以下是 Kotlin 中函数的基本用法:
- 函数声明
 
Kotlin 中的函数声明包括函数名称、参数列表、返回类型和函数体。下面是一个简单的函数示例:
1  | fun sum(a: Int, b: Int): Int {  | 
在这个例子中,sum 是函数名,(a: Int, b: Int) 是参数列表,Int 是返回类型,a + b 是函数体。
- 函数调用
 
调用函数时,可以像下面这样使用:
1  | val result = sum(3, 5)  | 
- 默认参数和命名参数
 
Kotlin 允许为函数的参数设置默认值,也支持通过参数名进行调用。例如:
1  | fun greet(name: String = "Guest", greeting: String = "Hello") {  | 
- 可变数量的参数
 
Kotlin 支持通过 vararg 关键字来定义可变数量的参数:
1  | fun printNumbers(vararg numbers: Int) {  | 
- 单表达式函数
 
对于单表达式的函数,可以简化成如下形式:
1  | fun multiply(a: Int, b: Int): Int = a * b  | 
- Lambda 表达式
 
Kotlin 支持使用 Lambda 表达式来定义匿名函数:
1  | val square: (Int) -> Int = { x -> x * x }  | 
- 高阶函数
 
Kotlin 支持高阶函数,可以将函数作为参数传递给其他函数,或者从函数中返回一个函数。
1  | fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {  | 
- 尾递归函数
 
Kotlin 支持尾递归函数,可以使用 tailrec 修饰符标记递归函数,以便进行优化。
1  | tailrec fun factorial(n: Int, result: Int = 1): Int {  | 
类与对象
在 Kotlin 中,类和对象是面向对象编程的基本概念。以下是关于类和对象的基本知识:
- 类的声明
 
在 Kotlin 中,使用 class 关键字声明类。一个类可以包含属性、方法和初始化块。
1  | class Person {  | 
- 对象的创建
 
使用 new 关键字创建对象的过程在 Kotlin 中是隐式的,直接使用类名并提供适当的参数即可。
1  | val person = Person()  | 
或者可以在创建对象时初始化属性:
1  | val person = Person().apply {  | 
- 构造函数
 
Kotlin 的类可以有主构造函数和次构造函数。主构造函数通常在类头部声明,次构造函数使用 constructor 关键字。
主构造函数
1  | class Person(val name: String, val age: Int) {  | 
次构造函数
1  | class Person {  | 
访问和厲性修饰符
在 Kotlin 中,访问修饰符和可见性修饰符用于控制代码中各个元素(类、函数、属性等)的可见性和访问权限。以下是 Kotlin 中常见的修饰符:
- 可见性修饰符
 
在 Kotlin 中,有四种可见性修饰符:
- public: 默认的修饰符,对所有可见。
 - internal: 模块内可见,一个模块是一组一起编译的 Kotlin 文件。
 - protected: 类内部和子类可见。
 - private: 类内部可见。
 
示例:
1  | class Example {  | 
- 成员修饰符
 
在 Kotlin 中,成员修饰符主要包括 public、internal、protected、private 以及 protected internal。
示例:
1  | class Example {  | 
- 模块内可见性
 
使用 internal 修饰符,一个模块内的所有代码都能访问该属性或函数。
1  | // ModuleA.kt  | 
- 顶层声明的可见性
 
在 Kotlin 中,顶层声明(没有包装在类或函数内的声明)的可见性受限于它所在的包的可见性。
1  | // file1.kt  | 
- 构造函数的可见性
 
类的主构造函数的可见性默认是 public,如果你希望构造函数有其他可见性,可以使用相应的修饰符。
1  | class Example private constructor(private val value: Int) {  | 
这些修饰符和规则有助于在 Kotlin 中控制代码的访问和可见性,以确保代码的封装性和安全性。
类的继承与重写
- 类的继承
 
在 Kotlin 中,使用 :(冒号)来表示类的继承关系。一个类可以继承另一个类,而被继承的类称为父类(或超类),继承的类称为子类。
示例:
1  | // 父类  | 
在这个例子中,Dog 类继承了 Animal 类,并且重写了 makeSound 方法。
open关键字
在 Kotlin 中,如果你希望一个类可以被继承,或者一个方法可以被子类重写,需要使用 open 关键字。在上面的例子中,Animal 类和 makeSound 方法都被标记为 open。
- 重写方法
 
在子类中,通过 override 关键字来重写父类中的方法。
注意方法前面也需要加上open
示例:
1  | // 父类  | 
super关键字
在子类中,使用 super 关键字可以调用父类的方法或属性。
示例:
1  | open class Animal {  | 
抽象,嵌套和内部类
- 抽象类(Abstract Classes)
 
抽象类是不能被实例化的类,它通常用于定义一些通用的行为,但需要在子类中进行具体的实现。在 Kotlin 中,使用 abstract 关键字声明抽象类和抽象方法。
示例:
1  | abstract class Shape {  | 
- 嵌套类(Nested Classes)
 
在 Kotlin 中,使用 class 关键字嵌套在另一个类中定义的类称为嵌套类。嵌套类不能访问外部类的实例,它类似于 Java 中的静态嵌套类。
注意使用的话不需要 Outer() 可以直接通过Outer 获取到他的嵌套类
示例:
1  | class Outer {  | 
- 内部类(Inner Classes)
 
内部类是在另一个类内部声明的类,并且可以访问外部类的实例。在 Kotlin 中,使用 inner 关键字声明内部类。
示例:
1  | class Outer {  | 
内部类可以访问外部类的成员,包括私有成员。在内部类中,使用 this@Outer 来引用外部类的实例。
总结
- 抽象类: 用于定义一些通用的行为,需要在子类中进行具体的实现。
 - 嵌套类: 在另一个类中定义的类,类似于静态嵌套类,无法访问外部类的实例。
 - 内部类: 在另一个类中定义的类,可以访问外部类的实例,使用 
inner关键字声明。 
这些特殊的类形式提供了不同的语法和行为,可以根据具体的需求选择适当的形式。
接口与接口实现
在 Kotlin 中,接口是一种定义抽象方法和属性的方式,而接口的实现则通过类来完成。以下是关于接口和接口实现的基本知识:
- 定义接口
 
在 Kotlin 中,使用 interface 关键字来定义接口。接口可以包含抽象方法、属性、以及具有默认实现的方法。
示例:
1  | interface Shape {  | 
- 实现接口
 
使用 class 关键字来实现一个接口,类可以实现多个接口。
示例:
1  | class Circle(override val name: String) : Shape {  | 
- 接口中的属性
 
接口可以包含抽象属性和具有默认实现的属性。实现类可以选择实现或重写这些属性。
示例:
1  | interface Shape {  | 
- 接口的继承
 
接口可以继承其他接口,使用冒号 : 来表示继承关系。实现类需要实现所有继承的接口中的抽象成员。
示例:
1  | interface Draggable {  | 
数据类,伴生类,枚举类
在 Kotlin 中,数据类(Data Classes)、伴生对象(Companion Objects)和枚举类(Enum Classes)是语言提供的一些特殊类形式,用于简化和增强代码。以下是关于这些类形式的详细说明:
- 数据类(Data Classes)
 
数据类是一种用于存储数据的特殊类,它自动提供一些通用的方法,如 toString()、equals()、hashCode() 等,以及自动生成 componentN() 函数用于解构。
示例:
1  | data class Person(val name: String, val age: Int)  | 
- 伴生对象(Companion Objects)
 
在 Kotlin 中,每个类都可以有一个伴生对象,通过 companion object 关键字声明。伴生对象类似于 Java 中的静态成员,但可以访问类的私有成员。
示例:
1  | class MyClass {  | 
- 枚举类(Enum Classes)
 
枚举类是一种用于表示一组常量的特殊类。枚举类可以包含属性、方法,每个枚举常量都是对象,这点比 java 要灵活的多。
示例:
1  | enum class Color(val rgb: Int) {  | 
枚举类的每个常量都是该枚举类的实例,可以拥有自己的属性和方法。
总结
- 数据类(Data Classes): 用于存储数据,自动生成通用方法。
 - 伴生对象(Companion Objects): 为类提供静态成员,可以访问类的私有成员。
 - 枚举类(Enum Classes): 用于表示一组常量,每个常量是该枚举类的实例,可以包含属性和方法。
 
这些特殊的类形式使得在 Kotlin 中编写清晰、简洁和易读的代码变得更加容易。
单例和对象表达式
在 Kotlin 中,单例对象和对象表达式都是用于创建单一实例的方式,但它们有不同的使用场景和特性。
- 单例对象
 
在 Kotlin 中,通过使用 object 关键字,可以创建一个单例对象。这个对象在程序运行期间只有一个实例,它在第一次被访问时被创建。
正是因为只有一个实例,所以实际上单例对象里的属性就达成了全局变量的效果,这一点使得单例对象将会被经常用到
示例:
1  | object MySingleton {  | 
- 对象表达式
 
对象表达式用于创建一个匿名对象,通常在需要一个对象实例而不需要显式声明一个新类的情况下使用。对象表达式可以实现一个接口或继承一个类。
示例:
1  | interface MyInterface {  | 
在上述示例中,createObject 函数返回一个实现了 MyInterface 接口的匿名对象。
总结
- 单例对象: 使用 
object关键字,用于创建一个全局唯一的实例,通常用于共享的资源或全局操作。 - 对象表达式: 用于创建一个匿名对象,通常在需要一个对象实例而不需要显式声明新类的情况下使用。
 
这些概念提供了在 Kotlin 中创建单一实例的不同方式,使得代码更加灵活和可读。
密封类和密封接口
在 Kotlin 中,密封类(Sealed Classes)和密封接口(Sealed Interfaces)是用于表示受限制的继承结构的概念,它们限制了继承结构的层次,通常用于在编写代码时提供更强大的静态分析。
- 密封类(Sealed Classes)
 
密封类是一种特殊的类,用于表示有限的继承结构,即这些类的子类是有限的,所有子类都需要在同一个文件中声明。密封类用 sealed 关键字声明。
示例:
1  | sealed class Result {  | 
在上述示例中,Result 是密封类,它有两个子类 Success 和 Error。由于密封类的所有子类都在同一个文件中,因此 when 表达式中的分支是完备的,不需要添加 else 分支。
- 密封接口(Sealed Interfaces)
 
密封接口是一种类似的概念,它也限制了接口的实现类的层次结构。密封接口使用 sealed 关键字声明。
示例:
1  | sealed interface Result {  | 
密封接口的使用方式类似于密封类,但密封接口通常用于接口层次结构的限制。
总结
- 密封类: 使用 
sealed关键字声明,用于表示有限的继承结构,所有子类需要在同一个文件中声明。 - 密封接口: 使用 
sealed关键字声明,用于表示有限的接口实现结构,所有实现类需要在同一个文件中声明。 
这些概念在编写代码时可以提供更强大的模型,确保在使用继承结构时更加安全和可靠。
扩展函数
在 Kotlin 中,扩展函数是一种强大的功能,允许你在不修改类的源代码的情况下,向类添加新的函数。扩展函数通过 receiverType.functionName 的形式定义。
最关键的在于这样就可以去封装一些比较基础的类下面的方法,这点是 java 做不到的
以下是一些关于扩展函数的基本知识:
- 定义扩展函数
 
1  | // 在 String 类上定义扩展函数  | 
上述示例中,addExclamation 是对 String 类的扩展函数,而 swap 是对 MutableList<T> 类的扩展函数。
- 使用扩展函数
 
1  | val greeting = "Hello"  | 
- 可空接收者类型
 
扩展函数可以被声明为可空接收者类型,这样它就可以在 null 对象上调用,而不会引发空指针异常。
1  | fun String?.printLength() {  | 
- 扩展函数的作用域
 
- 在文件顶层声明: 扩展函数可以在任何地方声明,不必在类内部。
 - 在某个类内声明: 如果在类内部声明扩展函数,那么这个函数只在该类的作用域内可用。
 - 在伴生对象中声明: 如果在类的伴生对象内声明扩展函数,它将在整个类的作用域内可用。
 
- 扩展函数不会真正修改类
 
尽管扩展函数被调用时看起来像是在类内部定义的方法,但它们实际上并没有修改类的源代码。它们是静态解析的,因此不会引入运行时的性能开销。
1  | class MyClass  | 
这些是关于 Kotlin 中扩展函数的基本知识。扩展函数是一种强大的工具,使得在不修改类的情况下,为现有类添加新功能变得更加方便。
对比 java
1、.equal 不需要了,kotlin 可以直接使用 == 号实现一样的功能