如何在 Kotlin 中定义自定义赋值运算符重载?

aSe*_*emy 3 operator-overloading assignment-operator kotlin gradle-kotlin-dsl

我有一个包含可变值的 Kotlin 类。

class StringWrapper(
  var value: String = ""
) {
  override fun toString(): String = value
}
Run Code Online (Sandbox Code Playgroud)

我使用这个包装器作为自定义数据持有者类中的属性

class DataHolder {
  val name = StringWrapper()

  override fun toString(): String = "Data(name=$name)"
}
Run Code Online (Sandbox Code Playgroud)

我想让它更容易地为内容赋值StringWrapper

val dataAlpha = DataHolder()

// the '.value' is unnecessary noise
dataAlpha.name.value = "alpha"

// I want to directly assign a string value, but I get an error
dataAlpha.name = "alpha"  // ERROR Type mismatch. 
                          //   Required: StringWrapper 
                          //   Found: String
Run Code Online (Sandbox Code Playgroud)

我还想让复制一个StringWrapper到另一个变得更容易。

val dataAlpha = DataHolder()
dataAlpha.name = "alpha"

val dataAlphaCopy = DataHolder()

// I want to directly assign the value of `dataAlpha.name.value` into `dataAlphaCopy.name.value`
dataAlphaCopy.name = dataAlpha.name // ERROR Val cannot be reassigned
Run Code Online (Sandbox Code Playgroud)

据我所知,Gradle 8.1在 Kotlin DSL 中有一个新的实验性功能,可以满足我的需求。如何在我自己的库中引入相同的赋值运算符?

我尝试查看运算符重载文档,但没有引用赋值运算符。

有一个KEEP 语言提案引入了这样的功能,但它已被关闭

我正在使用 Kotlin 1.8.20

aSe*_*emy 5

有一个新的 Kotlin (v1.8.0) 编译器插件可用于提供运算符加载。

目前尚未公布,但已可供使用。它位于此处的 Kotlin 源代码中。它与 Gradle 在Gradle 版本 8.1 的Kotlin DSL 中使用的插件相同。

IntelliJ 中的支持可能有限 - 请确保您使用的是最新版本。

应用 Kotlin 赋值编译器插件

Kotlin 分配插件可以像其他 Kotlin 编译器插件一样应用。

在 Gradle 项目中,可以使用简单的 Gradle 插件来应用它。

我不熟悉使用 Maven、Ant 或通过 CLI 编译 Kotlin - 但请查看其他 Kotlin 编译器插件说明以获取类似说明

ehemient 在 Slack 上共享了 CLI 指令

$KOTLIN_HOME/bin/kotlinc-jvm -Xplugin=$KOTLIN_HOME/lib/assignment-compiler-plugin.jar -P plugin:org.jetbrains.kotlin.assignment:annotation=fqdn.SupportsKotlinAssignmentOverloading ...
Run Code Online (Sandbox Code Playgroud)

(您可以在 https://github.com/JetBrains/kotlin/blob/master/plugins/assign-plugin/assign-plugin.common/src/org/jetbrains/kotlin/assignment/plugin/AssignmentPluginNames 中查看所有字符串。千吨

设置赋值重载

首先在你的项目中创建一个注释

package my.project

/** Denotes types that will be processed by the Kotlin Assignment plugin */
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class KotlinAssignmentOverloadTarget
Run Code Online (Sandbox Code Playgroud)

然后在 Gradle 中应用该插件,并将其配置为使用您的注释

plugins {
  kotlin("plugin.assignment") version "1.8.10"
}

assignment {
  annotation("my.project.KotlinAssignmentOverloadTarget")
}
Run Code Online (Sandbox Code Playgroud)

然后在您的代码中将注释应用到StringWrapper.

package my.project

/** Denotes types that will be processed by the Kotlin Assignment plugin */
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class KotlinAssignmentOverloadTarget
Run Code Online (Sandbox Code Playgroud)

我建议通常应将注释应用于包含一个(或可能多个)可变值的“包装器”类。它还可以应用于其他类似类实现的接口(这就是 Gradle 所做的)。

编写赋值运算符

应用编译器插件并设置注释后,您就可以开始编写重载运算符。

plugins {
  kotlin("plugin.assignment") version "1.8.10"
}

assignment {
  annotation("my.project.KotlinAssignmentOverloadTarget")
}
Run Code Online (Sandbox Code Playgroud)

使用赋值运算符

您现在可以直接将字符串分配给name属性

@KotlinAssignmentOverloadTarget
class StringWrapper(
  var value: String = ""
) {
  override fun toString(): String = value
}
Run Code Online (Sandbox Code Playgroud)

并且,使用扩展功能,将一个分配StringWrapper给另一个。

@KotlinAssignmentOverloadTarget
class StringWrapper(
  var value: String = ""
) {

  // member function
  /** Provides overloaded setter for setting the value of [value] using an assignment syntax */
  fun assign(value: String) {
    this.value = value
  }
}

// extension function
/** Provides overloaded setter for setting the value of [value] using an assignment syntax */
fun StringWrapper.assign(value: StringWrapper) {
  this.value = value.value
}
Run Code Online (Sandbox Code Playgroud)

局限性

属性不能可变

StringWrapper当类型的属性为s时,运算符重载将不起作用var。他们一定是vals。

val dataAlpha = DataHolder()
dataAlpha.name = "alpha"
println(dataAlpha) // prints: Data(name=alpha)
Run Code Online (Sandbox Code Playgroud)

赋值重载仅适用于成员属性

请记住,赋值重载在属性是成员属性时才起作用。

因此,当存在StringWrapper不是类属性的值,赋值重载器将不起作用。

val dataAlphaCopy = DataHolder()
dataAlphaCopy.name = dataAlpha.name
println(dataAlphaCopy) // prints: Data(name=alpha)
Run Code Online (Sandbox Code Playgroud)

手动调用该assign()函数,或创建一个具有属性的类。使用对象表达式也可以。

class MutableDataHolder {
  var name = StringWrapper()
}

fun main() {
  val dataAlpha = MutableDataHolder()

  // no overload operator is generated, because name is a 'var'
  dataAlpha.name = "alpha" // ERROR Type mismatch.
}
Run Code Online (Sandbox Code Playgroud)