Kotlin 学习

基本语法

变量与常量

变量使用var,常量使用val

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val a: Int = 1 // 立即赋值
val b = 2 // 自动推断出 `Int` 类型
val c: Int // 如果没有初始值类型不能省略
c = 3 // 明确赋值
println("a = $a, b = $b, c = $c")


var x = 5 // 自动推断出 `Int` 类型
x += 1
println("x = $x")
}

字符串模版

字符串模板使用 ${} 语法

1
2
3
val a = 10
val b = 5
println("Sum of $a and $b is ${a + b}") // 输出:Sum of 10 and 5 is 15

多行文本字符串

1
2
3
4
5
6
7
8
val multiLineString = """
This is a multi-line
string in Kotlin.
It preserves line breaks
and indentation.
""".trimIndent() // String类型的扩展函数,trimIndent 会去除换行

println(multiLineString)

数组

原生类型数组

1
2
3
4
5
6
7
8
9
10
11
12
// 创建一个大小为5的整数数组
val intArray: IntArray = IntArray(5)
// 初始化数组元素
intArray[0] = 1
intArray[1] = 2
intArray[2] = 3
intArray[3] = 4
intArray[4] = 5

// 使用数组字面值创建
val intArray = intArrayOf(1, 2, 3, 4, 5)

对象数组

1
2
3
4
5
// 创建一个字符串数组
val stringArray: Array<String> = arrayOf("apple", "banana", "orange")

// 创建一个任意类型的数组
val mixedArray: Array<Any> = arrayOf(1, "hello", 3.14, true)

常见操作

  1. 访问数组元素
1
val value = intArray[2] // 获取下标为2的元素值
  1. 遍历数组
1
2
3
for (element in intArray) {
println(element)
}
  1. 使用数组的高阶函数
1
2
3
4
5
6
7
// 使用forEach遍历数组
intArray.forEach { element ->
println(element)
}

// 使用map创建一个新数组
val squaredArray = intArray.map { it * it }
  1. 数组大小
1
val size = intArray.size // 获取数组大小

集合

不可变集合

  1. List 有序集合,允许重复元素。

    1
    val list: List<String> = listOf("apple", "banana", "orange")
  2. Set 无序集合,不允许重复元素。

    1
    val set: Set<Int> = setOf(1, 2, 3, 4, 5)
  3. Map<K, V>: 键值对集合。

    1
    val map: Map<String, Int> = mapOf("one" to 1, "two" to 2, "three" to 3)

可变集合

  1. MutableList 可变的有序集合。

    1
    2
    val mutableList: MutableList<String> = mutableListOf("apple", "banana", "orange")
    mutableList.add("grape") // 添加元素
  2. MutableSet 可变的无序集合。

    1
    2
    val mutableSet: MutableSet<Int> = mutableSetOf(1, 2, 3, 4, 5)
    mutableSet.add(6) // 添加元素
  3. MutableMap<K, V>: 可变的键值对集合。

    使用 to 来定义键值对

    1
    2
    val mutableMap: MutableMap<String, Int> = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
    mutableMap["four"] = 4 // 添加键值对

常见操作

1、遍历集合

1
2
3
for (element in list) {
println(element)
}

2、使用高阶函数

1
2
3
4
5
6
7
// 使用forEach遍历集合
list.forEach { element ->
println(element)
}

// 使用map创建一个新集合
val doubledList = list.map { it.length * 2 }3、过滤元素
1
val filteredList = list.filter { it.startsWith("a") }

4、转换为其他类型

1
2
val setFromList = list.toSet()
val mapFromList = list.associateBy { it.first() }

空安全

  1. 可空类型(Nullable Types)

在 Kotlin 中,使用 ? 符号来表示一个变量可以为 null。例如:

1
val nullableString: String? = null
  1. 安全调用运算符(Safe Call Operator)

使用 ?. 运算符来调用一个可空类型的方法或属性,如果对象为 null,表达式会返回 null 而不是抛出异常。

1
val length: Int? = nullableString?.length
  1. Elvis 运算符

Elvis 运算符 ?: 用于在表达式为 null 时提供一个备用值:

1
val result: String = nullableString ?: "Default Value"
  1. 非空断言运算符(Non-null Assertion Operator)

!! 运算符用于将一个可空类型强制转换为非空类型,如果对象为 null,则会抛出 NullPointerException

1
val length: Int = nullableString!!.length

注意: 非空断言运算符应该谨慎使用,因为它会绕过 Kotlin 的空安全检查。

  1. 安全转换

使用 as? 运算符进行安全类型转换,如果转换失败,返回 null。

1
val intValue: Int? = stringValue as? Int
  1. lateinit 属性

lateinit 关键字用于标记属性为延迟初始化,允许在之后初始化,但在初始化之前访问该属性会抛出异常。

不推荐使用

1
lateinit var name: String
  1. 安全的类型检查和自动类型转换

使用 is 运算符进行安全的类型检查,并自动转换为非空类型:

1
2
3
4
if (nullableString is String) {
// 在这个块中,nullableString 自动转换为非空类型 String
val length: Int = nullableString.length
}

条件判断语句

在 Kotlin 中,条件判断语句主要有 if 表达式和 when 表达式。下面我将介绍这两种条件判断语句的用法:

  1. if 表达式

基本用法

1
2
3
4
5
6
7
8
9
val number = 10

if (number > 0) {
println("Number is positive")
} else if (number < 0) {
println("Number is negative")
} else {
println("Number is zero")
}

作为表达式

if 语句在 Kotlin 中是一个表达式,可以用于赋值:

1
2
val result = if (number > 0) "Positive" else "Non-positive"
println(result)
  1. when 表达式

基本用法

1
2
3
4
5
6
7
8
9
val x = 5

when (x) {
1 -> println("One")
2 -> println("Two")
3, 4 -> println("Three or Four") // 匹配多个值
in 5..10 -> println("Between 5 and 10") // 匹配范围
else -> println("Other")
}

使用表达式作为分支条件

1
2
3
4
5
6
7
val result = when (x) {
in 1..5 -> "Small"
in 6..10 -> "Medium"
else -> "Large"
}

println("Result: $result")

使用 is 关键字进行类型检查

1
2
3
4
5
6
7
val value: Any = "Hello"

when (value) {
is String -> println("It's a string")
is Int -> println("It's an integer")
else -> println("Unknown type")
}

没有参数的 when 表达式

如果 when 表达式没有参数,它的分支条件是布尔表达式:

1
2
3
4
5
6
val isEven = when {
x % 2 == 0 -> true
else -> false
}

println("Is $x even? $isEven")

in 和 区间

区间表示一个连续的数值范围。在 Kotlin 中,区间可以表示为使用 .. 运算符的形式。

半开区间

1
2
3
4
5
val range = 1..5 // [1, 2, 3, 4, 5]

if (3 in range) {
println("3 is in the range")
}

开区间

1
2
3
4
5
6
val openRange = 1 until 5 // [1, 2, 3, 4]
val openRange = 1 ..< 5 // 这是1.8版本更新后的开区间表示方法,推荐使用这个

if (5 !in openRange) {
println("5 is not in the open range")
}

自定义步长的区间

1
2
3
4
5
val customRange = 10 downTo 1 step 2 // [10, 8, 6, 4, 2]

if (6 in customRange) {
println("6 is in the custom range")
}

循环控制

在 Kotlin 中,有两种主要的循环结构:for 循环和 while 循环。此外,还有一种 do-while 循环。以下是这些循环结构的基本用法:

  1. for 循环

遍历集合或数组

1
2
3
4
5
val numbers = listOf(1, 2, 3, 4, 5)

for (number in numbers) {
println(number)
}

使用 .. 创建数值范围

1
2
3
for (i in 1..5) {
println(i)
}

使用 downTostep 创建递减和自定义步长的范围

1
2
3
for (i in 5 downTo 1 step 2) {
println(i)
}
  1. while 循环

基本用法

1
2
3
4
5
6
var x = 0

while (x < 5) {
println(x)
x++
}
  1. do-while 循环

基本用法

1
2
3
4
5
6
var y = 0

do {
println(y)
y++
} while (y < 5)
  1. foreach循环
  2. 控制流语句

在 Kotlin 中,forEach 是集合类(包括数组)提供的一个扩展函数,用于遍历集合中的元素。这是一种更具 Kotlin 风格的循环语法。以下是 forEach 的用法:

1
2
3
4
5
6
7
8
9
10
val numbers = listOf(1, 2, 3, 4, 5)

// 使用 forEach 遍历集合
numbers.forEach { number ->
println(number)
}
// 注意在 forEach 内部默认 it 为内置的元素
numbers.forEach {
println(it)
}

在上面的例子中,forEach 接受一个 lambda 表达式作为参数,该 lambda 表达式会对集合中的每个元素执行相应的操作。在 lambda 表达式中,number 是集合中的元素。

对于数组,forEach 同样适用:

1
2
3
4
5
val numbersArray = intArrayOf(1, 2, 3, 4, 5)

numbersArray.forEach { number ->
println(number)
}

forEach 使得集合的遍历更加简洁和易读,是 Kotlin 中推荐的一种方式。需要注意的是,forEach 是只读的,不能用于修改集合中的元素。如果需要修改元素,可以使用 forEachIndexed,该函数提供了元素索引的信息:

1
2
3
4
5
6
7
val mutableList = mutableListOf(1, 2, 3, 4, 5)

mutableList.forEachIndexed { index, value ->
mutableList[index] = value * 2
}

println(mutableList) // 输出 [2, 4, 6, 8, 10]

这样,forEach 可以方便地遍历集合中的元素,而 forEachIndexed 则提供了对元素索引的访问。

breakcontinue

在循环中使用 break 可以退出循环,而 continue 可以跳过当前迭代。

1
2
3
4
5
6
7
8
9
for (i in 1..10) {
if (i == 5) {
break // 退出循环
}
if (i % 2 == 0) {
continue // 跳过当前迭代,进入下一次循环
}
println(i)
}

return 标签

在嵌套循环中,可以使用 return 标签从最外层的函数或 lambda 表达式中返回。

1
2
3
4
5
6
7
8
9
10
fun foo() {
outer@ for (i in 1..5) {
for (j in 1..5) {
if (i * j > 10) {
return@outer // 从 foo 函数返回
}
println(i * j)
}
}
}

函数

在 Kotlin 中,函数是一等公民,具有许多强大的特性。以下是 Kotlin 中函数的基本用法:

  1. 函数声明

Kotlin 中的函数声明包括函数名称、参数列表、返回类型和函数体。下面是一个简单的函数示例:

1
2
3
fun sum(a: Int, b: Int): Int {
return a + b
}

在这个例子中,sum 是函数名,(a: Int, b: Int) 是参数列表,Int 是返回类型,a + b 是函数体。

  1. 函数调用

调用函数时,可以像下面这样使用:

1
2
val result = sum(3, 5)
println(result) // 输出 8
  1. 默认参数和命名参数

Kotlin 允许为函数的参数设置默认值,也支持通过参数名进行调用。例如:

1
2
3
4
5
6
7
fun greet(name: String = "Guest", greeting: String = "Hello") {
println("$greeting, $name!")
}

greet() // 输出 "Hello, Guest!"
greet("John") // 输出 "Hello, John!"
greet(greeting = "Hi", name = "Alice") // 输出 "Hi, Alice!"
  1. 可变数量的参数

Kotlin 支持通过 vararg 关键字来定义可变数量的参数:

1
2
3
4
5
6
7
fun printNumbers(vararg numbers: Int) {
for (number in numbers) {
println(number)
}
}

printNumbers(1, 2, 3, 4, 5)
  1. 单表达式函数

对于单表达式的函数,可以简化成如下形式:

1
fun multiply(a: Int, b: Int): Int = a * b
  1. Lambda 表达式

Kotlin 支持使用 Lambda 表达式来定义匿名函数:

1
2
val square: (Int) -> Int = { x -> x * x }
println(square(5)) // 输出 25
  1. 高阶函数

Kotlin 支持高阶函数,可以将函数作为参数传递给其他函数,或者从函数中返回一个函数。

1
2
3
4
5
6
fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}

val result = operateOnNumbers(3, 5) { x, y -> x + y }
println(result) // 输出 8
  1. 尾递归函数

Kotlin 支持尾递归函数,可以使用 tailrec 修饰符标记递归函数,以便进行优化。

1
2
3
tailrec fun factorial(n: Int, result: Int = 1): Int {
return if (n == 0) result else factorial(n - 1, n * result)
}

类与对象

在 Kotlin 中,类和对象是面向对象编程的基本概念。以下是关于类和对象的基本知识:

  1. 类的声明

在 Kotlin 中,使用 class 关键字声明类。一个类可以包含属性、方法和初始化块。

1
2
3
4
5
6
7
8
9
10
class Person {
// 属性
var name: String = ""
var age: Int = 0

// 方法
fun speak() {
println("Hello, my name is $name and I'm $age years old.")
}
}
  1. 对象的创建

使用 new 关键字创建对象的过程在 Kotlin 中是隐式的,直接使用类名并提供适当的参数即可。

1
2
3
4
val person = Person()
person.name = "John"
person.age = 30
person.speak()

或者可以在创建对象时初始化属性:

1
2
3
4
5
val person = Person().apply {
name = "John"
age = 30
}
person.speak()
  1. 构造函数

Kotlin 的类可以有主构造函数和次构造函数。主构造函数通常在类头部声明,次构造函数使用 constructor 关键字。

主构造函数

1
2
3
class Person(val name: String, val age: Int) {
// ...
}

次构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person {
var name: String = ""
var age: Int = 0

constructor(name: String, age: Int) {
this.name = name
this.age = age
}
}
// 实际上上面的并不是次构造函数,因为上面的 Person 没有括号,实际上没有主构造函数,上面的 constructor 就变成了主构造函数
// 下面的才是次构造函数,注意这个 this,作为次构造函数需要为主构造函数提供需要的值,但是我主函数没有需要的值,所以直接 this()
class Person() {
var name: String = ""
var age: Int = 0

constructor(name: String, age: Int):this() {
this.name = name
this.age = age
}
}

访问和厲性修饰符

在 Kotlin 中,访问修饰符和可见性修饰符用于控制代码中各个元素(类、函数、属性等)的可见性和访问权限。以下是 Kotlin 中常见的修饰符:

  1. 可见性修饰符

在 Kotlin 中,有四种可见性修饰符:

  • public: 默认的修饰符,对所有可见。
  • internal: 模块内可见,一个模块是一组一起编译的 Kotlin 文件。
  • protected: 类内部和子类可见。
  • private: 类内部可见。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Example {
// 默认是 public
val publicVar = 10

// internal 修饰符
internal val internalVar = 20

// protected 修饰符
protected val protectedVar = 30

// private 修饰符
private val privateVar = 40
}
  1. 成员修饰符

在 Kotlin 中,成员修饰符主要包括 publicinternalprotectedprivate 以及 protected internal

示例:

1
2
3
4
5
6
7
8
9
10
11
class Example {
val publicVar = 10 // 默认是 public

internal val internalVar = 20 // internal 修饰符

protected val protectedVar = 30 // protected 修饰符

private val privateVar = 40 // private 修饰符

protected internal val protectedInternalVar = 50 // protected internal 修饰符
}
  1. 模块内可见性

使用 internal 修饰符,一个模块内的所有代码都能访问该属性或函数。

1
2
3
4
5
6
7
// ModuleA.kt
internal val moduleVariable = "I'm internal"

// ModuleB.kt
fun accessModuleVariable() {
println(moduleVariable) // 在同一模块内可以访问
}
  1. 顶层声明的可见性

在 Kotlin 中,顶层声明(没有包装在类或函数内的声明)的可见性受限于它所在的包的可见性。

1
2
3
4
5
6
7
8
9
// file1.kt
private val topLevelVariable = "I'm private"

// file2.kt
fun accessTopLevelVariable() {
// 不能在其他文件中访问 topLevelVariable
// 会导致编译错误
// println(topLevelVariable)
}
  1. 构造函数的可见性

类的主构造函数的可见性默认是 public,如果你希望构造函数有其他可见性,可以使用相应的修饰符。

1
2
3
4
5
6
class Example private constructor(private val value: Int) {
// ...
}

// 在其他文件中无法直接调用主构造函数
// val example = Example(42)

这些修饰符和规则有助于在 Kotlin 中控制代码的访问和可见性,以确保代码的封装性和安全性。

类的继承与重写

  1. 类的继承

在 Kotlin 中,使用 :(冒号)来表示类的继承关系。一个类可以继承另一个类,而被继承的类称为父类(或超类),继承的类称为子类。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 父类
open class Animal(val name: String) {
open fun makeSound() {
println("Animal makes a sound")
}
}

// 子类
class Dog(name: String, val breed: String) : Animal(name) {
override fun makeSound() {
println("Dog barks")
}
}

在这个例子中,Dog 类继承了 Animal 类,并且重写了 makeSound 方法。

  1. open 关键字

在 Kotlin 中,如果你希望一个类可以被继承,或者一个方法可以被子类重写,需要使用 open 关键字。在上面的例子中,Animal 类和 makeSound 方法都被标记为 open

  1. 重写方法

在子类中,通过 override 关键字来重写父类中的方法。

注意方法前面也需要加上open

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 父类
open class Animal {
open fun makeSound() {
println("Animal makes a sound")
}
}

// 子类
class Dog : Animal() {
override fun makeSound() {
println("Dog barks")
}
}

// 使用
val myDog = Dog()
myDog.makeSound() // 输出 "Dog barks"
  1. super 关键字

在子类中,使用 super 关键字可以调用父类的方法或属性。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
open class Animal {
open fun makeSound() {
println("Animal makes a sound")
}
}

class Dog : Animal() {
override fun makeSound() {
super.makeSound() // 调用父类的方法
println("Dog barks")
}
}

// 使用
val myDog = Dog()
myDog.makeSound()

抽象,嵌套和内部类

  1. 抽象类(Abstract Classes)

抽象类是不能被实例化的类,它通常用于定义一些通用的行为,但需要在子类中进行具体的实现。在 Kotlin 中,使用 abstract 关键字声明抽象类和抽象方法。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class Shape {
abstract fun draw() // 抽象方法
}

class Circle : Shape() {
override fun draw() {
println("Drawing a circle")
}
}

// 使用
val myCircle = Circle()
myCircle.draw() // 输出 "Drawing a circle"
  1. 嵌套类(Nested Classes)

在 Kotlin 中,使用 class 关键字嵌套在另一个类中定义的类称为嵌套类。嵌套类不能访问外部类的实例,它类似于 Java 中的静态嵌套类。

注意使用的话不需要 Outer() 可以直接通过Outer 获取到他的嵌套类

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Outer {
private val outerVar: Int = 10

class Nested {
fun accessOuterVar(outer: Outer) {
// 无法直接访问 outerVar,因为 Nested 类没有引用到 Outer 的实例
// println(outer.outerVar)
}
}
}

// 使用
val nested = Outer.Nested()
  1. 内部类(Inner Classes)

内部类是在另一个类内部声明的类,并且可以访问外部类的实例。在 Kotlin 中,使用 inner 关键字声明内部类。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Outer {
private val outerVar: Int = 10

inner class Inner {
fun accessOuterVar() {
println(outerVar)
}
}
}

// 使用
val outer = Outer()
val inner = outer.Inner()
inner.accessOuterVar() // 输出 10

内部类可以访问外部类的成员,包括私有成员。在内部类中,使用 this@Outer 来引用外部类的实例。

总结

  • 抽象类: 用于定义一些通用的行为,需要在子类中进行具体的实现。
  • 嵌套类: 在另一个类中定义的类,类似于静态嵌套类,无法访问外部类的实例。
  • 内部类: 在另一个类中定义的类,可以访问外部类的实例,使用 inner 关键字声明。

这些特殊的类形式提供了不同的语法和行为,可以根据具体的需求选择适当的形式。

接口与接口实现

在 Kotlin 中,接口是一种定义抽象方法和属性的方式,而接口的实现则通过类来完成。以下是关于接口和接口实现的基本知识:

  1. 定义接口

在 Kotlin 中,使用 interface 关键字来定义接口。接口可以包含抽象方法、属性、以及具有默认实现的方法。

示例:

1
2
3
4
5
6
7
8
9
interface Shape {
val name: String // 抽象属性

fun draw() // 抽象方法

fun resize() {
println("Resizing $name") // 默认实现
}
}
  1. 实现接口

使用 class 关键字来实现一个接口,类可以实现多个接口。

示例:

1
2
3
4
5
6
7
8
9
10
11
class Circle(override val name: String) : Shape {
override fun draw() {
println("Drawing a circle")
}
}

class Rectangle(override val name: String) : Shape {
override fun draw() {
println("Drawing a rectangle")
}
}
  1. 接口中的属性

接口可以包含抽象属性和具有默认实现的属性。实现类可以选择实现或重写这些属性。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Shape {
val name: String // 抽象属性

val color: String // 具有默认实现的属性
get() = "Red"
}

class Circle(override val name: String) : Shape {
// 可以选择实现 color 属性
}

class Rectangle(override val name: String, override val color: String) : Shape {
// 可以选择重写 color 属性
}
  1. 接口的继承

接口可以继承其他接口,使用冒号 : 来表示继承关系。实现类需要实现所有继承的接口中的抽象成员。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Draggable {
fun drag()
}

interface Resizable {
fun resize()
}

class Button : Draggable, Resizable {
override fun drag() {
println("Dragging button")
}

override fun resize() {
println("Resizing button")
}
}

数据类,伴生类,枚举类

在 Kotlin 中,数据类(Data Classes)、伴生对象(Companion Objects)和枚举类(Enum Classes)是语言提供的一些特殊类形式,用于简化和增强代码。以下是关于这些类形式的详细说明:

  1. 数据类(Data Classes)

数据类是一种用于存储数据的特殊类,它自动提供一些通用的方法,如 toString()equals()hashCode() 等,以及自动生成 componentN() 函数用于解构。

示例:

1
2
3
4
5
6
7
8
data class Person(val name: String, val age: Int)

// 使用
val person1 = Person("John", 30)
val person2 = Person("John", 30)

println(person1) // 输出 "Person(name=John, age=30)"
println(person1 == person2) // 输出 "true"
  1. 伴生对象(Companion Objects)

在 Kotlin 中,每个类都可以有一个伴生对象,通过 companion object 关键字声明。伴生对象类似于 Java 中的静态成员,但可以访问类的私有成员。

示例:

1
2
3
4
5
6
7
8
class MyClass {
companion object {
fun create(): MyClass = MyClass()
}
}

// 使用
val instance = MyClass.create()
  1. 枚举类(Enum Classes)

枚举类是一种用于表示一组常量的特殊类。枚举类可以包含属性、方法,每个枚举常量都是对象,这点比 java 要灵活的多。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF);

fun printColor() {
println("RGB: $rgb")
}
}

// 使用
val color = Color.RED
println(color) // 输出 "RED"
color.printColor() // 输出 "RGB: 16711680"

枚举类的每个常量都是该枚举类的实例,可以拥有自己的属性和方法。

总结

  • 数据类(Data Classes): 用于存储数据,自动生成通用方法。
  • 伴生对象(Companion Objects): 为类提供静态成员,可以访问类的私有成员。
  • 枚举类(Enum Classes): 用于表示一组常量,每个常量是该枚举类的实例,可以包含属性和方法。

这些特殊的类形式使得在 Kotlin 中编写清晰、简洁和易读的代码变得更加容易。

单例和对象表达式

在 Kotlin 中,单例对象和对象表达式都是用于创建单一实例的方式,但它们有不同的使用场景和特性。

  1. 单例对象

在 Kotlin 中,通过使用 object 关键字,可以创建一个单例对象。这个对象在程序运行期间只有一个实例,它在第一次被访问时被创建。

正是因为只有一个实例,所以实际上单例对象里的属性就达成了全局变量的效果,这一点使得单例对象将会被经常用到

示例:

1
2
3
4
5
6
7
8
object MySingleton {
fun doSomething() {
println("Doing something in MySingleton")
}
}

// 使用
MySingleton.doSomething()
  1. 对象表达式

对象表达式用于创建一个匿名对象,通常在需要一个对象实例而不需要显式声明一个新类的情况下使用。对象表达式可以实现一个接口或继承一个类。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface MyInterface {
fun doSomething()
}

fun createObject(): MyInterface {
return object : MyInterface {
override fun doSomething() {
println("Doing something in anonymous object")
}
}
}

// 使用
val myObject = createObject()
myObject.doSomething()

在上述示例中,createObject 函数返回一个实现了 MyInterface 接口的匿名对象。

总结

  • 单例对象: 使用 object 关键字,用于创建一个全局唯一的实例,通常用于共享的资源或全局操作。
  • 对象表达式: 用于创建一个匿名对象,通常在需要一个对象实例而不需要显式声明新类的情况下使用。

这些概念提供了在 Kotlin 中创建单一实例的不同方式,使得代码更加灵活和可读。

密封类和密封接口

在 Kotlin 中,密封类(Sealed Classes)和密封接口(Sealed Interfaces)是用于表示受限制的继承结构的概念,它们限制了继承结构的层次,通常用于在编写代码时提供更强大的静态分析。

  1. 密封类(Sealed Classes)

密封类是一种特殊的类,用于表示有限的继承结构,即这些类的子类是有限的,所有子类都需要在同一个文件中声明。密封类用 sealed 关键字声明。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sealed class Result {
class Success(val data: String) : Result()
class Error(val message: String) : Result()
}

// 使用
fun processResult(result: Result) {
when (result) {
is Result.Success -> println("Success: ${result.data}")
is Result.Error -> println("Error: ${result.message}")
}
}

// 创建实例
val successResult: Result = Result.Success("Data")
val errorResult: Result = Result.Error("Error Message")

// 处理实例
processResult(successResult)
processResult(errorResult)

在上述示例中,Result 是密封类,它有两个子类 SuccessError。由于密封类的所有子类都在同一个文件中,因此 when 表达式中的分支是完备的,不需要添加 else 分支。

  1. 密封接口(Sealed Interfaces)

密封接口是一种类似的概念,它也限制了接口的实现类的层次结构。密封接口使用 sealed 关键字声明。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sealed interface Result {
class Success(val data: String) : Result
class Error(val message: String) : Result
}

// 使用
fun processResult(result: Result) {
when (result) {
is Result.Success -> println("Success: ${result.data}")
is Result.Error -> println("Error: ${result.message}")
}
}

// 创建实例
val successResult: Result = Result.Success("Data")
val errorResult: Result = Result.Error("Error Message")

// 处理实例
processResult(successResult)
processResult(errorResult)

密封接口的使用方式类似于密封类,但密封接口通常用于接口层次结构的限制。

总结

  • 密封类: 使用 sealed 关键字声明,用于表示有限的继承结构,所有子类需要在同一个文件中声明。
  • 密封接口: 使用 sealed 关键字声明,用于表示有限的接口实现结构,所有实现类需要在同一个文件中声明。

这些概念在编写代码时可以提供更强大的模型,确保在使用继承结构时更加安全和可靠。

扩展函数

在 Kotlin 中,扩展函数是一种强大的功能,允许你在不修改类的源代码的情况下,向类添加新的函数。扩展函数通过 receiverType.functionName 的形式定义。

最关键的在于这样就可以去封装一些比较基础的类下面的方法,这点是 java 做不到的

以下是一些关于扩展函数的基本知识:

  1. 定义扩展函数
1
2
3
4
5
6
7
8
9
10
11
// 在 String 类上定义扩展函数
fun String.addExclamation(): String {
return "$this!"
}

// 在 MutableList 类上定义扩展函数
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}

上述示例中,addExclamation 是对 String 类的扩展函数,而 swap 是对 MutableList<T> 类的扩展函数。

  1. 使用扩展函数
1
2
3
4
5
6
7
val greeting = "Hello"
val modifiedGreeting = greeting.addExclamation()
println(modifiedGreeting) // 输出 "Hello!"

val numbers = mutableListOf(1, 2, 3, 4)
numbers.swap(0, 2)
println(numbers) // 输出 [3, 2, 1, 4]
  1. 可空接收者类型

扩展函数可以被声明为可空接收者类型,这样它就可以在 null 对象上调用,而不会引发空指针异常。

1
2
3
4
5
6
7
8
9
10
fun String?.printLength() {
if (this == null) {
println("String is null")
} else {
println("Length of the string is ${this.length}")
}
}

val nullableString: String? = null
nullableString.printLength() // 输出 "String is null"
  1. 扩展函数的作用域
  • 在文件顶层声明: 扩展函数可以在任何地方声明,不必在类内部。
  • 在某个类内声明: 如果在类内部声明扩展函数,那么这个函数只在该类的作用域内可用。
  • 在伴生对象中声明: 如果在类的伴生对象内声明扩展函数,它将在整个类的作用域内可用。
  1. 扩展函数不会真正修改类

尽管扩展函数被调用时看起来像是在类内部定义的方法,但它们实际上并没有修改类的源代码。它们是静态解析的,因此不会引入运行时的性能开销。

1
2
3
4
5
6
7
8
9
10
class MyClass

// 定义扩展函数
fun MyClass.printMessage() {
println("Hello from extension function!")
}

// 使用扩展函数
val myInstance = MyClass()
myInstance.printMessage() // 输出 "Hello from extension function!"

这些是关于 Kotlin 中扩展函数的基本知识。扩展函数是一种强大的工具,使得在不修改类的情况下,为现有类添加新功能变得更加方便。

对比 java

1、.equal 不需要了,kotlin 可以直接使用 == 号实现一样的功能