kotlin 的拓展函数,为这个语言带来极大的可玩性。可以极大的减少现有类的大小, 比如 string这个类 在Android 的 java实现中 有三千多行
但是在 Kotlin 版本中的 String类只有50多行,不到一屏。是不是匪夷所思
那,我们同样调用的那些 string 的方法是怎么做到无痛调用的呢? 答案就是- 函数拓展。
kotlin 的 string 的 function 一部分的实现都在kotlin.text.Strings.kt中作为拓展函数来实现,还有一些是在StringsJVM.kt 中实现。
所以我们利用拓展函数也能实现基本的拓展方法,也能为现有的类新增新的方法(本质上是一个顶层方法,类似于静态方法,到处都能引用)比如为字符串增加一个判断是否是邮箱的拓展方法如下。
// 自定义 String 类的扩展函数,用于判断是否是合法的电子邮件格式 fun String.isValidEmail(): Boolean { // 简单的正则表达式,用于匹配电子邮件格式 val emailRegex = "^[A-Za-z](.*)([@])(.+)(\\.)(.+)".toRegex() return this.matches(emailRegex) } fun main() { val email = "example@example.com" val invalidEmail = "example.com" // 使用自定义的扩展函数来判断字符串是否为合法的电子邮件 println(email.isValidEmail()) // 输出: true println(invalidEmail.isValidEmail()) // 输出: false }
但是也有局限性,就是扩展函数不能重写父类的成员函数,且它们在继承结构中不会被子类覆盖。
open class Parent class Child : Parent() fun Parent.foo() = "Parent" fun Child.foo() = "Child" fun main() { val p: Parent = Child() println(p.foo()) // 输出: "Parent" 而不是 "Child" }
拓展函数也能拓展属性,但本质上只是一个 get/set 方法,不会真正修改对象的内存结构。
val String.firstChar: Char get() = this[0] fun main() { val str = "Hello" println(str.firstChar) // 输出: 'H' }
这种机制让 Kotlin 既保持了与 Java 的兼容性,同时又提供了极大的灵活性,可以让开发者在不修改现有类的情况下为其添加新功能。
正儿八经的事情说完了,但是今天要介绍的是一些奇技淫巧。下面开始
1.解构(destructure)数据
什么是解构,我们使用一对括号,放入我们的变量,然后直接=
一个对象,就是一段解构声明。Kotlin会自动按照类变量声明的顺序,将相关的值取出赋值给=
左边的变量。看一下如下例子就懂了
data class User(val id:Int, val name:String) fun main() { val user = User(10086, "hugh") val (uid, name) = user println(uid)//10086 println(name)//hugh println(user.component1())//10086 println(user.component2())//hugh }
还可以在 map 中使用,可能遍历一个映射(map)最好的方式就是这样:
for ((key, value) in map) { // 使用该 key、value 做些事情 }
首先它是怎么实现的呢? 标准库提供了这样的扩展:
operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator() operator fun <K, V> Map.Entry<K, V>.component1() = getKey() operator fun <K, V> Map.Entry<K, V>.component2() = getValue()
上面对应的拓展了操作符 for/函数名iterator 和操作符 ()/ 解构声明 component1
所以使用 component 我们可以对任何数据进行解构,比如要想实现如下数据
根据提示,我们可以实现拓展 LocalDate 类的 操作符 component1,component2,component3
operator fun LocalDate.component1()= year operator fun LocalDate.component2()= monthValue operator fun LocalDate.component3()= dayOfMonth
以上就能使编译通过,并且正确执行了。我们不一定需要定义成 data class
2. 字符串也变成函数?/invoke
"hello world"()
我们可以拓展一个对象的invoke方法,来实现对对象进行调用。
operator fun Any?.invoke() { println(this) }
上面例子可以让任意对象变成一个函数调用,都可以在控制台输出
"hello world"() 1() null() true()
于是,便可以实现如下效果
val exp = 3(2 + 6) println(exp)
3. 通过索引来找目标/ get
你应该写过如下的代码
val list = arrayListOf(1, 2, 3) println(list[1])
如果想通过如下代码从一个数字中,找到第几位,比如从 321 中提取出个位,十位,百位如下代码是不是看起来很简洁
val count = 123 println(count[3])//0 println(count[2])//1 println(count[1])//2
它可以通过如下方式重载操作符,让上面表达式生效
operator fun Int.get(idx: Int): Int = div(10.0.pow(idx.toDouble())).rem(10).toInt()
除了对变量进行拓展 get 方法,也可以对函数表达式进行拓展
val double: (Int) -> Int = { it * 2 } println(double(12))
让这个变量也可以实现奇怪的表达式
operator fun ((Int) -> Int).get(idx:Int):Int{ return this(idx) } println(double[12])//24 operator fun ((Int) -> Int).invoke(call: () -> Int): Int { return this(call()) } print(double { 12 })//24
4. 像自然语言一样写语句 /contains,in
回到之前的例子
val data = LocalDate.parse("2024-09-17")
如果我们需要判断这个日期在不在哪一年哪一月,可以用如下语句判断
operator fun Month.contains(date: LocalDate): Boolean { return this == date.month } operator fun Int.contains(date: LocalDate): Boolean { return this == date.year } infix fun Month.of(year: Int): Pair<Month, Int> = this to year //中缀函数 operator fun Pair<Month, Int>.contains(date: LocalDate): Boolean { return this.first == date.month && this.second == date.year } println(data in Month.OCTOBER)//false println(data in 2024)//true println(data in Month.SEPTEMBER of 2024)//true val p = Month.MAY of 2023
这个例子,拓展了 年月份的判断 contains, 使用中缀函数,增加了 of 来返回一个 pair 再拓展 pair 的contains 方法就可以达到 像英语一样写表达式了。
结语
Kotlin 中提供了一套常见的可重载操作符,以下是一些常用的操作符及其对应的函数:
在 Kotlin 中,operator
关键字用于重载操作符。这意味着你可以自定义类的某些操作符行为,使得你的类可以像基础类型一样使用操作符(例如 +
, -
, *
, []
, ++
, --
等)。
Kotlin 中的操作符重载并不是修改现有的操作符,而是通过定义一些特殊的函数(通常带有 operator
关键字),让你能够为自定义的类赋予特定的操作符行为。Kotlin 提供了一套可以重载的操作符列表,每个操作符都有其对应的函数名称。
常见的可重载操作符
Kotlin 中提供了一套常见的可重载操作符,以下是一些常用的操作符及其对应的函数:
操作符 | 对应函数名 | 示例 |
---|---|---|
+ | plus | a + b |
- | minus | a - b |
* | times | a * b |
/ | div | a / b |
% | rem 或 mod | a % b |
++ | inc | a++ |
-- | dec | a-- |
[] | get / set | a[i] / a[i] = value |
== | equals | a == b |
> | compareTo | a > b |
in | contains | a in b |