MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Kotlin标准库函数与扩展函数

2024-08-235.7k 阅读

Kotlin 标准库函数

集合操作函数

  1. map 函数
    • 本质map函数用于将集合中的每个元素按照给定的变换规则映射为新的元素,返回一个新的集合。它遍历原始集合,对每个元素应用指定的函数,并将结果收集到一个新的集合中。
    • 代码示例
val numbers = listOf(1, 2, 3, 4)
val squaredNumbers = numbers.map { it * it }
println(squaredNumbers)
  • 在上述代码中,numbers是一个包含整数的列表。map函数接受一个Lambda表达式{ it * it },该表达式将每个元素平方。最终,squaredNumbers是一个新的列表,包含[1, 4, 9, 16]
  1. filter 函数
    • 本质filter函数用于从集合中筛选出满足特定条件的元素,返回一个新的集合。它遍历原始集合,对每个元素应用给定的判断条件,只有满足条件的元素才会被包含在新的集合中。
    • 代码示例
val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers)
  • 这里,filter函数通过Lambda表达式{ it % 2 == 0 }筛选出numbers列表中的偶数,evenNumbers最终为[2, 4]
  1. fold 函数
    • 本质fold函数是一种通用的折叠操作,它从一个初始值开始,依次对集合中的每个元素应用一个二元操作符,将集合“折叠”成一个单一的值。这个二元操作符接受两个参数:累加器(初始值或上一次操作的结果)和集合中的当前元素。
    • 代码示例
val numbers = listOf(1, 2, 3, 4)
val sum = numbers.fold(0) { acc, num -> acc + num }
println(sum)
  • 在这个例子中,初始值为0fold函数依次将列表中的元素与累加器acc相加,最终sum的值为1 + 2+3 + 4 = 10

字符串处理函数

  1. split 函数
    • 本质split函数用于根据指定的分隔符将字符串拆分成子字符串,并返回一个字符串列表。分隔符可以是字符、字符串或者正则表达式。
    • 代码示例
val sentence = "Hello, World! How are you?"
val words = sentence.split(", ")
println(words)
  • 上述代码中,split函数根据, 作为分隔符,将sentence字符串拆分成["Hello", "World! How are you?"]这样一个列表。
  1. trim 函数
    • 本质trim函数用于去除字符串两端的空白字符(空格、制表符、换行符等)。它返回一个新的字符串,不改变原始字符串。
    • 代码示例
val text = "   Kotlin is great!   "
val trimmedText = text.trim()
println(trimmedText)
  • 这里,trim函数去除了text字符串两端的空白字符,trimmedText"Kotlin is great!"
  1. replace 函数
    • 本质replace函数用于将字符串中指定的字符或字符串替换为新的字符或字符串。它返回一个新的字符串,原始字符串保持不变。
    • 代码示例
val original = "I like apples"
val replaced = original.replace("apples", "oranges")
println(replaced)
  • 在这个例子中,replace函数将original字符串中的"apples"替换为"oranges"replaced"I like oranges"

数值处理函数

  1. rangeTo 函数
    • 本质rangeTo函数用于创建一个范围,它接受两个参数,左边界和右边界,返回一个ClosedRange类型的对象,表示从左边界到右边界(包括两端)的范围。
    • 代码示例
val range = 1.rangeTo(5)
for (i in range) {
    println(i)
}
  • 上述代码创建了一个从15的范围,通过for循环可以遍历这个范围内的所有整数,依次输出12345
  1. coerceIn 函数
    • 本质coerceIn函数用于将一个数值限制在指定的范围内。如果该数值小于范围的下限,则返回下限;如果大于范围的上限,则返回上限;否则返回该数值本身。
    • 代码示例
val number = 15
val result = number.coerceIn(10, 20)
println(result)
  • 这里number15,在1020的范围内,所以result15。如果number5,则result10;如果number25,则result20
  1. roundToInt 函数
    • 本质roundToInt函数用于将一个浮点数四舍五入为最接近的整数。如果小数部分大于或等于0.5,则向上取整;否则向下取整。
    • 代码示例
val floatNumber = 3.6
val roundedInt = floatNumber.roundToInt()
println(roundedInt)
  • 对于floatNumber3.6roundToInt函数将其向上取整为4并输出。

Kotlin 扩展函数

扩展函数的基本概念

  1. 定义与本质
    • 扩展函数允许我们在不修改类的源代码的情况下,为现有的类添加新的函数。它本质上是一种静态函数,只是在调用时可以使用类似实例方法的语法。扩展函数通过在函数名前加上要扩展的类名作为接收者类型来定义。
    • 代码示例
fun String.addPrefix(prefix: String): String {
    return prefix + this
}

val text = "Kotlin"
val prefixedText = text.addPrefix("Hello, ")
println(prefixedText)
  • 在上述代码中,我们为String类定义了一个扩展函数addPrefix。这个函数接受一个prefix参数,并将其与调用该函数的字符串实例连接起来。text是一个String实例,通过text.addPrefix("Hello, ")的方式调用扩展函数,得到"Hello, Kotlin"
  1. 作用域与可见性
    • 扩展函数的作用域与定义它的位置有关。如果在顶级(文件级)定义扩展函数,它对整个模块可见(在Kotlin模块内)。如果在类内部定义扩展函数,它的可见性与类成员的可见性规则相同。
    • 代码示例
class Outer {
    fun String.innerExtension(): String {
        return "Inner extension on $this"
    }

    fun test() {
        val text = "test"
        val result = text.innerExtension()
        println(result)
    }
}

val outer = Outer()
outer.test()
  • 在这个例子中,innerExtension是在Outer类内部为String定义的扩展函数,它只能在Outer类的内部使用。Outer类的test方法中可以调用text.innerExtension(),输出"Inner extension on test"

扩展函数的应用场景

  1. 增强现有类的功能
    • 当我们使用一些第三方库的类,而这些类没有我们需要的特定功能时,可以通过扩展函数来添加功能。例如,假设我们使用一个表示日期的类Date(这里假设为自定义简单日期类),但它没有获取日期是星期几的功能。
    • 代码示例
class Date(val year: Int, val month: Int, val day: Int)

fun Date.getDayOfWeek(): String {
    // 简单的示例,实际实现需要更复杂的算法
    val days = arrayOf("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
    return days[(year + month + day) % 7]
}

val date = Date(2023, 10, 15)
val dayOfWeek = date.getDayOfWeek()
println(dayOfWeek)
  • 这里我们为Date类定义了getDayOfWeek扩展函数,使其具备获取日期是星期几的功能。
  1. 提高代码的可读性和可维护性
    • 对于一些通用的操作,可以通过扩展函数封装起来,使代码更加简洁和易读。比如,在处理字符串时,经常需要判断字符串是否为空且只包含空白字符。
    • 代码示例
fun String.isBlankOrEmpty(): Boolean {
    return this.trim().isEmpty()
}

val text1 = "   "
val text2 = "Kotlin"
println(text1.isBlankOrEmpty())
println(text2.isBlankOrEmpty())
  • 通过定义isBlankOrEmpty扩展函数,在判断字符串时,代码变得更加直观,text1.isBlankOrEmpty()返回truetext2.isBlankOrEmpty()返回false

扩展函数与成员函数的优先级

  1. 规则
    • 当一个类既有成员函数又有扩展函数,并且它们具有相同的签名(函数名和参数列表相同)时,成员函数的优先级高于扩展函数。这是因为成员函数是类定义的一部分,而扩展函数是在类外部添加的。
    • 代码示例
class MyClass {
    fun greet() {
        println("Member function greet")
    }
}

fun MyClass.greet() {
    println("Extension function greet")
}

val myObject = MyClass()
myObject.greet()
  • 在上述代码中,尽管我们定义了一个与MyClass类的成员函数greet同名的扩展函数,但myObject.greet()调用的是成员函数,输出"Member function greet"
  1. 特殊情况
    • 如果扩展函数定义在一个类内部,并且该类包含与扩展函数同名的成员函数,在这个类内部调用该函数时,会优先调用成员函数。但如果在类外部,通过类的实例调用该函数,同样是成员函数优先。不过,如果使用扩展函数的接收者类型作为限定符,可以调用到扩展函数。
    • 代码示例
class Outer {
    class Inner {
        fun greet() {
            println("Inner member greet")
        }
    }

    fun Inner.extendedGreet() {
        println("Inner extended greet")
    }

    fun test() {
        val inner = Inner()
        inner.greet()
        // 调用成员函数

        Inner::extendedGreet.invoke(inner)
        // 调用扩展函数
    }
}

val outer = Outer()
outer.test()
  • Outer类的test方法中,inner.greet()调用的是Inner类的成员函数,而通过Inner::extendedGreet.invoke(inner)这种方式调用到了扩展函数extendedGreet

扩展函数的类型检查

  1. is 关键字的使用
    • 在扩展函数中,有时需要根据对象的实际类型进行不同的操作。可以使用is关键字进行类型检查。例如,为一个接口定义扩展函数,而该接口有多个实现类,我们可能需要在扩展函数中针对不同的实现类做不同处理。
    • 代码示例
interface Shape
class Circle : Shape
class Rectangle : Shape

fun Shape.printDetails() {
    if (this is Circle) {
        println("This is a circle")
    } else if (this is Rectangle) {
        println("This is a rectangle")
    }
}

val circle = Circle()
val rectangle = Rectangle()

circle.printDetails()
rectangle.printDetails()
  • Shape接口的扩展函数printDetails中,通过is关键字检查this的实际类型,从而输出不同的信息。circle.printDetails()输出"This is a circle"rectangle.printDetails()输出"This is a rectangle"
  1. 类型转换与安全调用
    • 在扩展函数中进行类型检查后,可能需要将对象转换为具体类型以访问特定的属性或方法。可以使用as关键字进行类型转换,但要注意可能的类型转换异常。为了避免异常,可以使用安全类型转换as?和安全调用?.
    • 代码示例
interface Animal
class Dog : Animal {
    fun bark() {
        println("Woof!")
    }
}

fun Animal.makeSound() {
    if (this is Dog) {
        (this as Dog).bark()
        // 普通类型转换

        (this as? Dog)?.bark()
        // 安全类型转换和安全调用
    }
}

val dog = Dog()
val animal: Animal = dog
animal.makeSound()
  • Animal接口的扩展函数makeSound中,通过as进行普通类型转换调用bark方法,同时也展示了as??.的安全用法。在这个例子中,两种方式都能正确调用Dogbark方法输出"Woof!"

扩展函数与泛型

  1. 泛型扩展函数的定义
    • 可以定义泛型扩展函数,使其适用于多种类型。泛型扩展函数在函数定义前声明泛型参数,就像普通泛型函数一样。
    • 代码示例
fun <T> List<T>.firstAndLast(): Pair<T, T>? {
    if (this.isEmpty()) {
        return null
    }
    return Pair(this.first(), this.last())
}

val numbers = listOf(1, 2, 3)
val result = numbers.firstAndLast()
result?.let { (first, last) ->
    println("First: $first, Last: $last")
}
  • 在上述代码中,firstAndLast是为List<T>定义的泛型扩展函数,它返回列表的第一个和最后一个元素组成的Pair。如果列表为空则返回null。这里numbers是一个List<Int>result如果不为null,则可以获取到第一个和最后一个元素并输出。
  1. 泛型约束在扩展函数中的应用
    • 可以对泛型扩展函数的类型参数添加约束,以限制可以使用的类型。例如,我们可能希望泛型类型必须实现某个接口。
    • 代码示例
interface Printable {
    fun print()
}

class Message : Printable {
    override fun print() {
        println("This is a message")
    }
}

fun <T : Printable> List<T>.printAll() {
    for (item in this) {
        item.print()
    }
}

val messages = listOf(Message())
messages.printAll()
  • 这里printAll是为List<T>定义的泛型扩展函数,其中T必须实现Printable接口。messages列表中的元素都是Message类型,实现了Printable接口,printAll函数可以调用列表中每个元素的print方法。

扩展函数的局限性

  1. 无法访问私有成员
    • 扩展函数虽然可以为类添加新功能,但它无法访问类的私有成员。这是因为扩展函数本质上是静态函数,不是类的一部分,没有访问类内部私有状态的权限。
    • 代码示例
class PrivateClass {
    private val privateValue = 10

    fun publicFunction() {
        println("Public function")
    }
}

fun PrivateClass.extensionFunction() {
    publicFunction()
    // 可以调用公有函数

    // 以下代码会报错,无法访问私有成员
    // println(privateValue)
}

val privateObject = PrivateClass()
privateObject.extensionFunction()
  • extensionFunction扩展函数中,只能调用PrivateClass的公有函数publicFunction,无法访问私有成员privateValue
  1. 无法覆盖成员函数
    • 如前面提到的,扩展函数不能覆盖类的成员函数。即使扩展函数与成员函数签名相同,调用时也是成员函数优先。这就限制了我们通过扩展函数改变类原有的核心行为。
    • 代码示例
class OverrideTest {
    fun printMessage() {
        println("Original member function")
    }
}

fun OverrideTest.printMessage() {
    println("Extension function")
}

val overrideObject = OverrideTest()
overrideObject.printMessage()
  • 这里overrideObject.printMessage()调用的是OverrideTest类的成员函数,输出"Original member function",扩展函数无法覆盖成员函数。