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 可以直接使用 == 号实现一样的功能