在Kotlin,我可以覆盖一些现有的运营商,但是如何创建新的运营商呢?

Jay*_*ard 6 operator-overloading kotlin

在Kotlin中,我看到我可以覆盖一些运算符,例如+通过函数plus()*函数times()...但是对于像集合这样的东西,首选(集合论)符号/运算符不存在.例如A?B,交集和A?B联合.

我似乎无法定义自己的运算符,没有明确的语法来说明用于运算符的符号.例如,如果我想$$为运算符创建一个函数:

operator fun String.$$(other: String) = "$this !!whatever!! $other"
// or even
operator fun String.whatever(other: String) = "$this !!whatever!! $other" // how do I say this is the $$ symbol?!?
Run Code Online (Sandbox Code Playgroud)

我得到两个相同的错误:

错误:(y,x)Kotlin:'operator'修饰符不适用于此函数:非法函数名称

可以创建或覆盖运营商的规则是什么?

注意: 这个问题是由作者故意编写和回答的(答案问题),因此对于常见问题的Kotlin主题的惯用答案存在于SO中.

Jay*_*ard 6

Kotlin只允许覆盖一组非常特定的运算符,并且您无法更改可用运算符列表.

当覆盖您试图保持原始运算符精神的运算符或数学符号的其他常见用法时,您应该小心.但有时候典型的符号不可用.例如,集合Union ?可以很容易地被视为+因为它在概念上是有意义的,并且它是Set<T>.plus()Kotlin已经提供的内置运算符,或者您可以获得创造性并在这种情况下使用中缀函数:

// already provided by Kotlin:
// operator fun <T> Set<T>.plus(elements: Iterable<T>): Set<T> 

// and now add my new one, lower case 'u' is pretty similar to math symbol ?
infix fun <T> Set<T>.u(elements: Set<T>): Set<T> = this.plus(elements)

// and therefore use any of...
val union1 = setOf(1,2,5) u setOf(3,6)
val union2 = setOf(1,2,5) + setOf(3,6)  
val union3 = setOf(1,2,5) plus setOf(3,6)
Run Code Online (Sandbox Code Playgroud)

或者更清楚的是:

infix fun <T> Set<T>.union(elements: Set<T>): Set<T> = this.plus(elements)

// and therefore
val union4 = setOf(1,2,5) union setOf(3,6)
Run Code Online (Sandbox Code Playgroud)

继续你的Set运算符列表,交集是符号?所以假设每个程序员都有一个字母,字母'n'看起来?我们可以逃脱:

infix fun <T> Set<T>.n(elements: Set<T>): Set<T> = this.intersect(elements)

// and therefore...
val intersect = setOf(1,3,5) n setOf(3,5)
Run Code Online (Sandbox Code Playgroud)

或通过运算符重载*as:

operator fun <T> Set<T>.times(elements: Set<T>): Set<T> = this.intersect(elements)  

// and therefore...
val intersect = setOf(1,3,5) * setOf(3,5)
Run Code Online (Sandbox Code Playgroud)

虽然您已经可以使用现有的标准库中缀函数intersect():

val intersect = setOf(1,3,5) intersect setOf(3,5)
Run Code Online (Sandbox Code Playgroud)

如果您正在发明新的东西,则需要选择最接近的运算符或函数名称.例如,否定一组枚举,可能使用-operator(unaryMinus())或!operator(not()):

enum class Things {
    ONE, TWO, THREE, FOUR, FIVE
}

operator fun Set<Things>.unaryMinus() = Things.values().toSet().minus(this)
operator fun Set<Things>.not() = Things.values().toSet().minus(this)

// and therefore use any of...

val current = setOf(Things.THREE, Things.FIVE)

println(-current)             // [ONE, TWO, FOUR]
println(-(-current))          // [THREE, FIVE]
println(!current)             // [ONE, TWO, FOUR]
println(!!current)            // [THREE, FIVE]
println(current.not())        // [ONE, TWO, FOUR]
println(current.not().not())  // [THREE, FIVE]
Run Code Online (Sandbox Code Playgroud)

因为操作符重载可能非常有用,所以要周到,否则会导致混乱和混乱.您必须在保持代码可读性的同时确定最佳选择.有时,如果运算符符合该符号的标准,或者与原始符号类似的中缀替换,或使用描述性单词以便不存在混淆,则运算符最佳.

始终检查Kotlin Stdlib API参考,因为您可能已经定义了许多运算符,或者具有等效的扩展函数.

另一件事......

关于您的$$操作员,从技术上讲,您可以这样做:

infix fun String.`$$`(other: String) = "$this !!whatever!! $other"
Run Code Online (Sandbox Code Playgroud)

但是因为你需要转义函数的名称,所以调用它会很难看:

val text = "you should do" `$$` "you want"
Run Code Online (Sandbox Code Playgroud)

这不是真正的操作符重载,只有当它是我可以infix创建的函数时才会起作用.