科特林运算符重载and Conventions
介绍
科特林支持一种称为 约定, everyone should be familiar with. For example, if you define a special method plus
in your class, you can use the +
operator 按照惯例:Kotlin操作员重载。
在本文中,我想向您展示可以使用的约定,并且还将提供一些约定。 科特林 演示概念的代码示例。
科特林公约
科特林 defines 约定 that we can apply 通过 implementing methods that comply with predefined names like plus
. This is contrary to how 爪哇 does provide equivalent language features, which is 通过 specific classes like Iterable
to make objects usable in for
loops for example.
科特林选择的这种不同方法更加灵活,因为您始终可以 扩展现有课程 通过定义自己的 扩展功能 on existing types, whereas it is often not possible to extend classes with new implemented interfaces like Iterable
. Finally, I’m really starting to love 扩展功能…​ ðŸ™,
在以下描述中,我将使用代表 数学分数。本课程将 不 完整,也不会提供数学上完美的实现。以防万一,您感觉想知道更好的实现方法
资料类别 Fraction(val 分子: 在t, val 分母: 在t) {
val decimal 通过 懒 { 分子.toDouble() / 分母 }
override fun toString() = "$numerator/$denominator"
}
第一个修订版非常简单:我们有一个带有两个用户定义属性的数据类: 分子 和 分母. If we print a Fraction
to the console, it’s supposed to look like "2/3", so the toString()
is being overridden. Also, for comparing two Fraction
instances, a 懒 包含属性以提供分数的十进制值。
科特林运算符重载
算术运算符
Overloading operators enables us to use +
in other classes than 在t
or String
, you can use 科特林’s predefined naming 约定 to provide this functionality in any class. Let’s add it to our Fraction
和 see how it’s done.
资料类别 Fraction(val 分子: 在t, val 分母: 在t) {
//...
operator fun plus(add: Fraction) =
if (this.denominator == add.denominator){
Fraction(this.numerator + add.numerator, 分母)
} else {
val a = this * add.denominator //(1)
val b = add * this.denominator
Fraction(a.numerator + b.numerator, a.denominator)
}
operator fun times(num: 在t) = Fraction(numerator * num, 分母 * num)
}
As you can see here, the plus
method is defined as an 操作员功能 通过 using the operator
关键词, which is relevant because otherwise, the compiler wouldn’t treat plus
as a special function complying with a convention. As a second 二元 operator, times
is implemented 和 also used in the implementation of plus
, as you can see in (1). The statement this * add.denominator
is translated to this.times(add.denominator)
通过 the compiler.
Let’s see how the Fraction
can be used outside of this class now.
var sum = Fraction(2, 3) + Fraction(3, 2)
println("Sum: ${sum.decimal}")
>> prints "Sum: 2.1666666666666665"
As expected, adding two fractions with +
is now possible, as well as using *
would be as we also provided a times
function. If we wanted to be able to multiply a Fraction
with another Fraction
, we’d have to overload the times
function to accept a Fraction
parameter instead of an 在t
. 因此,提供具有不同参数类型的多个运算符功能不是问题.
除了 二元 运算符,我们也可以重载 一元 operators to do something like negating a fraction: -Fraction(2,3)
or incrementing/decrementing 通过 using --
/++
. Please have a look at the 文件资料 了解这些情况下的命名约定。
比较运算符
除了算术运算符,Kotlin的确使我们能够重载 比较 operators: ==
, >=
, <
和 so on. Thus, these operators can be used for any type, not only primitives as in 爪哇. Let’s see, how these 约定 look like.
平等
在 爪哇, we often don’t want to use ==
when comparing two instances of the same class because it tests for 指称相等. 在stead, equals
is used which can be overridden in order to have a way to compare objects for 结构平等. 在 科特林 on the other hand, it’s recommended to use ==
for 结构平等 和 ===
for 指称相等. This also relies on a convention because ==
is translated to a call of equals
under the hood.
该实现可自动用于 Fraction
资料类别. If we wanted to implement it ourselves, we would have to override the following function defined in Any
:
public open operator fun equals(other: Any?): Boolean
It’s defined as an operator
function as we can see, what makes it usable for ==
和 also !=
.
更多比较
If we want to compare two objects in 爪哇 to perform sorting, for example, we implement the 可比
interface with its compareTo
method. This is also done in 科特林, but with much better support 和 a shorthand syntax. If you implement this method in a class, you can use 所有 the nice operators like <, <=, >, >=
out of the box. These operators are translated to appropriate calls of compareTo
通过 the compiler: obj1 > obj2
⇒ obj1.compareTo(obj2) > 0
.
Let’s make Fraction
comparable 和 see how it can be used afterward.
资料类别 Fraction(val 分子: 在t, val 分母: 在t) : 可比<Fraction> {
//...
override fun compareTo(other: Fraction) = decimal.compareTo(other.decimal)
}
The implementation is really straightforward, because we can just compare the floating representation of the fractions to each other. The interface method compareTo
in 可比
 already defines the operator
 keyword, which makes it not necessary to add the 关键词 in the presented implementation. If we just implemented the method, without adding the 关键词, it would 不 possible to use Fraction
with the nice, short syntax described earlier. The next example will present its usage.
println("3/2 > 2/2: ${Fraction(3, 2) > Fraction(2, 2)}")
println("1/2 <= 2/4: ${Fraction(1, 2) <= Fraction(2, 4)}")
>> prints "3/2 > 2/2: true"
"1/2 <= 2/4: true"
You can see here, that it’s no problem to use the custom Fraction
instances in 比较s we only knew for primitives in 爪哇 before. This is made possible 通过 科特林’s compiler, which I appreciate a lot.
集合和范围
您可能已经注意到,在用Kotlin编写代码时,您可以使用 清单 和 地图 正如您从Java数组中了解到的,例如,使用 索引运算符。最重要的是,我们可以使用 in
关键词 检查元素是否是集合的一部分。这可以通过 约定, too. 科特林 knows the following operators for collections: set
和 get
for using 索引运算符s 和 contains
to enable in
.
Let’s try an 索引运算符 for our Fraction
class; as it doesn’t make sense to 所有ow mutability of instances of fractions, we will only provide the get
operator (whereas set
works exactly the same).
这次我们将为操作符使用扩展功能,因为这种方法非常重要,因为您可以始终使用所需的操作符扩展现有的类。
operator fun Fraction.get(ind: 在t) =
when (ind) {
0 -> 分子
1 -> 分母
else -> IllegalArgumentException("在dex must be 0 or 1")
}
As already mentioned, operators can have multiple overloaded versions for the same class, here we use a single integer parameter to receive either the 分子
or the 分母
of a fraction. It’s very easy again: just define a method complying with the naming convention "get" 和 mark it with the operator
关键词. Now we can access the properties with index syntax:
var sum = Fraction(2, 3) + Fraction(3, 2)
println("Sum 分子: ${sum[0]}")
>> prints "Sum 分子: 13"
As I told you earlier, 科特林 does also 所有ow to use in
for checking the existence of an element inside a collection. Unfortunately, this doesn’t make sense for a Fraction
. Let’s alternatively check how 科特林 defines such an operator for collections. 在 Collections.kt we can find an interface method Collection::contains
which is marked as an operator:
public operator fun contains(element: @UnsafeVariance E): Boolean
That’s it. Any subclass will have to provide an implementation 和 everyone will be able to use code like x in listOf(1,2,3)
because 清单 provide a contains
method as they implement Collection
.
我希望你能想到 科特林约定 目前为止。还有更多的东西要学习,有趣的一点是 范围.
范围
We can use the ..
syntax for creating 范围 in 科特林. This is also made possible 按照惯例s because ..
is translated to rangeTo
method calls internally. This means whenever you feel like wanting to enable your class to be usable with 范围, just implement an operator with the name "rangeTo" 和 you’ll be fine. If you already implemented the 可比
interface, this isn’t even necessary, because 科特林 provides a generic extension to 所有 可比类:
operator fun <T: 可比<T>> T.rangeTo(that: T): 关dRange<T>
As an example, let’s see how Fraction
can be used with 范围.
val fracRange = Fraction(1, 5)..Fraction(5, 7)
println(Fraction(3, 5) in fracRange)
>> prints "true"
就像我们之前看到的 Fraction
是 可比
类,使我们可以立即使用范围。
一个更复杂的任务是上课 可重复的 over 范围. This means we would like to use a range of fractions inside a for-loop. 在 科特林, every class providing an operator method iterator()
can be used in such loops. This also explains, how a String
can be used in a for-loop: 科特林 defines an extension method to it complying with the 约定.
Let’s see, how we can make Fraction
可重复的. As we know from the example 可比范围, using the rangeTo
syntax is going to create an instance of 关dRange<Fraction>
, which, 通过 default, doesn’t have an iterator()
function. I’ll show you how to create this method:
operator fun 关dRange<Fraction>.iterator() =
object : Iterator<Fraction> {
var curr: Fraction = start
override fun hasNext() = curr <= endInclusive
override fun next() = curr++
}
We just extend 关dRange<Fraction>
with the desired method 和 create an implementation of Iterator<Fraction>
which is produced 通过 this method.
之后,我们可以在循环中使用此类范围并打印其中的每个元素。
for (i in fracRange) {
println("Next: $i")
}
>> prints "Next: 1/5
Next: 2/5
Next: 3/5"
If you’re very intent on my examples, you might have noticed the use of ++
in the 迭代器扩展 代码清单。这是我们到目前为止尚未见过的运营商之一。不过,实现非常简单:
operator fun Fraction.inc() = Fraction(this.numerator + 1, this.denominator)
There you go, a not very reasonable implementation for incrementing fractions with the ++
operator ðŸ™,
破坏性声明
科特林的另一个功能似乎 魔法, 一开始是 破坏性声明。它通常在for循环中使用映射来呈现,从而将映射的条目破坏为键值对。看看这个:
val map = hashMapOf(1 to "single", 2 to "许多")
for ((i, s) in map){ // (1)
println("Entry in map: $i: $s")
}
在 (1) 你可以观察到 破坏性声明 in action: Entries of the map are destructed in two separate variables i
和 s
. So how does that work actually? You might have guessed it already: 约定.
When we make use of a 破坏性声明 (v1, v2, …​, vn)
the variables v1..vn are initialized 通过 calls to functions with the name component1
, component2
, componentN
. But how does a Map
declare those functions? Of course, 科特林’s 标准库 just adds extensions to Map
to enable this functionality.
public inline operator fun <K, V> Map.Entry<K, V>.component1(): K = 键
public inline operator fun <K, V> Map.Entry<K, V>.component2(): V = 值
The function component1
simply returns the 键 whereas component2
returns the 值, really easy once again. These functions are created automatically for every data
class 通过 default. The Fraction
class can be used in 破坏性声明, too.
val f = Fraction(2, 3)
val (a, b) = f
println("Destructed f to: ($a, $b)")
>> prints "Destructed f to: (2, 3)"
The componentX
functions are generated for every 在主要构造函数中声明的属性 数据类。
到目前为止,我们已经观察到最有趣的Kotlin约定是算术和比较运算符,集合和范围运算符,以及最后但并非最不重要的破坏声明。我们需要考虑的最后一项约定是 调用约定。我想这没有很多用例,但是一个重要的例子是 DSLs。下一章将提供相关信息。
(代表团 也依赖于约定,但是该主题值得单独发表。如果您有兴趣,请看看 文件资料,也许您可以发现约定)。
调用
正如我在上一篇文章中暗示的那样 带有接收器的功能文字,Kotlin旨在实现出色的(内部)创作 领域特定语言。除了带有接收器的函数文字外,启用DSL的另一个非常重要的语言功能是 调用约定.
简而言之,该约定使对象 可调用的功能. This feature does not make much sense in most cases, because it can lead to weird code. Anyway, let’s try it in the Fraction
class.
资料类别 Fraction(val 分子: 在t, val 分母: 在t) : 可比<Fraction> {
//...
operator fun 调用(prefix: String = "") = println(prefix + toString())
}
在 this 调用 操作员功能 a single parameter of type String
is defined. The parameter is used to prefix the toString()
representation of a Fraction
instance 和 print it to the console. So, how do we use this functionality?
var f = Fraction(2, 3)
f("My 调用 prefix: ")
>> prints "My 调用 prefix: 2/3"
有点奇怪,但是我们现在可以 调用 our fraction with a String
, which will print some output on the console. The compiler translates this call to the more comprehensive expression f.invoke("My 调用 prefix: ")
. You’re free to choose any signature for your 调用 methods, it’s also possible to return 值s from it.
您知道吗,Kotlin如何提供 功能类型 比Java更好的方法吗?看一下 Functions.kt:使用时 拉姆达s in your 科特林 code, they are compiled to Function
instances, which define 调用
methods. The following code demonstrates it:
val sum = { x: 在t, y: 在t -> x + y }
sum.invoke(3, 10)
sum(3, 10)
We define a simple function for adding two 在t
值s. This function can be called in two ways: 通过 just putting the parameters in parentheses after the function name, or 通过 explicitly calling 调用
. That’s because sum is a Function
under the hood. Awesome, isn’t it? I’m always happy when I understand some of 科特林’s magic.
As for DSLs, the reason for using 调用
is almost the same. It 所有ows providing 灵活 API的使用方式。
结论
在本文中,我打算向您介绍Kotlin如何使用特殊功能的命名约定,这使我们可以重载运算符,将复合值分解为单独的变量,使用适当的函数类型或使用范围。我认为理解这些概念非常重要,因为它可以提供 许多 explanations to some mysteries of 科特林 as a language. Also, anyone using the language will find some concepts useful for his code, I bet. Maybe you’ll now be able to simplify some of your existing code bases. From now on, you’ll always notice operator
关键词s in 科特林 😉
Simon是总部位于德国的软件工程师,具有7年为JVM和JavaScript编写代码的经验。他’他对尽可能多地学习新事物充满热情,并且是科特林(Kotlin)的自发狂热者。
注意,`public open operator fun equals(other:Any?):Boolean`将无效。要使用`==`运算符,您需要重写`equals()`方法,但不要’无需将其标记为“操作员”。正确的版本是`override fun equals(other:Any?):Boolean`
我保持纠正:这篇文章是正确的。抱歉,您没有阅读文字。
嗨,达尼尔,这实际上是在Any中(而不是在实现中)定义equals的方式。
对不起,如果我不清楚
It’有趣的是他们使用了component(1,2…)而不是component(0,1…)从0开始的索引。我感到困惑。
[…]资料来源:科特林公约–运算符重载– 西蒙·维尔茨 Blog […]
[…]功能,我也已经介绍过,称为带有Receiver的功能文字,其他功能是调用约定或infix […]
[…]功能,我也已经介绍过,称为带有Receiver的功能文字,其他功能是调用约定或infix […]
[…]运算符重载[…]