科特林运算符重载–按照公约工作

科特林运算符重载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: setget 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 is. 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 科特林 😉

如果您想看一下我的示例,可以在我的代码中找到该代码 的GitHub 资料库。随时提供任何反馈意见,我总是很乐意为您提供帮助。另外,如果你喜欢,看看我的 推特 帐户,如果您对更多Kotlin产品ðŸ™感兴趣,请关注。非常感谢。

如果您想了解有关Kotlin的美丽功能的更多信息,请推荐这本书 行动中的科特林 给你,也想带你去我的 其他文章 ðŸ™,

 


 

关于8条想法“科特林运算符重载–按照公约工作

发表评论

您的电子邮件地址不会被公开。 必需的地方已做标记 *