kotlin.操作员重载–公约致力于努力

kotlin.操作员重载and Conventions

介绍

kotlin.支持一种调用的技术 惯例, everyone should be familiar with. For example, if you define a special method plus in your class, you can use the + operator 按照惯例:Kotlin操作员重载。
在本文中,我想向您展示您可以使用的惯例,我还将提供一些 kotlin. 代码示例,用于演示概念。

kotlin惯例

kotlin. defines conventions that we can apply by implementing methods that comply with predefined names like plus. This is contrary to how java. does provide equivalent language features, which is by specific classes like Iterable to make objects usable in for loops for example.
由Kotlin选择的这种不同的方法,更灵活,因为你可以 延长现有课程 通过定义自己的 扩展功能 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 extension functions…​ 🙂

在以下描述中,我将使用代表一个类 数学分数。这个班级将是 不是 完成,也不会提供数学上完美的实现。以防万一,你觉得了解更好的实现 -

Dataclass Fraction.
数据类 Fraction(val numerator: Int, val denominator: Int) {
    val decimal by lazy { numerator.toDouble() / denominator }

    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 懒惰的 包括提供分数的小数值的财产。

kotlin运算符重载

算术运营商

Overloading operators enables us to use + in other classes than 在 t or String, you can use Kotlin’s predefined naming conventions to provide this functionality in any class. Let’s add it to our Fraction 和 see how it’s done.

二进制加运营商
数据类 Fraction(val numerator: Int, val denominator: Int) {
    //...

    operator fun plus(add: Fraction) =
            if (this.denominator == add.denominator){
                Fraction(this.numerator + add.numerator, denominator)
            } else {
                val a = this * add.denominator //(1)
                val b = add * this.denominator
                Fraction(a.numerator + b.numerator, a.denominator)
            }

    operator fun times(num: Int) = Fraction(numerator * num, denominator * num)

}

As you can see here, the plus method is defined as an 操作员功能 经过 using the operator keyword, which is relevant because otherwise, the compiler wouldn’t treat plus as a special function complying with a convention. As a second binary operator, times is implemented and 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 by 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 Java. Let’s see, how these conventions look like.

平等

在 Java, we often don’t want to use == when comparing two instances of the same class because it tests for 参考平等. Instead, equals is used which can be overridden in order to have a way to compare objects for 结构平等. In Kotlin on the other hand, it’s recommended to use == for structural equality and === for referential equality. 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 Java to perform sorting, for example, we implement the 可比 interface with its compareTo method. This is also done in Kotlin, but with much better support and a shorthand syntax. If you implement this method in a class, you can use all the nice operators like <, <=, >, >= out of the box. These operators are translated to appropriate calls of compareTo 经过 the compiler: obj1 > obj2obj1.compareTo(obj2) > 0.
Let’s make Fraction comparable and see how it can be used afterward.

可比
数据类 Fraction(val numerator: Int, val denominator: Int) : Comparable<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 keyword in the presented implementation. If we just implemented the method, without adding the keyword, it would not be 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 comparisons we only knew for primitives in Java before. This is made possible by Kotlin’s compiler, which I appreciate a lot.

汇集和范围

您可能已经注意到,同时在Kotlin编写代码,您可以使用 清单地图 例如,根据需要从Java阵列,使用 索引运营商。在那之上,我们可以使用 in keyword 要检查,元素是否是集合的一部分。这是可能的 惯例, too. Kotlin knows the following operators for collections: setget for using index operators and contains to enable in.
Let’s try an index operator for our Fraction class; as it doesn’t make sense to allow mutability of instances of fractions, we will only provide the get operator (whereas set works exactly the same).
这次我们将为操作员使用扩展功能,因为此方法非常重要,因为您始终可以使用所需的运算符将现有类扩展。

运营商扩展获取
operator fun Fraction.get(ind: Int) =
        when (ind) {
            0 -> numerator
            1 -> denominator
            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" and mark it with the operator keyword. Now we can access the properties with index syntax:

索引访问
var sum = Fraction(2, 3) + Fraction(3, 2)
println("Sum numerator: ${sum[0]}")
>> prints "Sum numerator: 13"

As I told you earlier, Kotlin does also allow 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 Kotlin defines such an operator for collections. In 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 and everyone will be able to use code like x in listOf(1,2,3) because lists provide a contains method as they implement Collection.

我希望你能得到这个想法 kotlin惯例 目前为止。还有更多的学习,一个有趣的点是 范围.

范围

We can use the .. syntax for creating ranges in Kotlin. This is also made possible by conventions because .. is translated to rangeTo method calls internally. This means whenever you feel like wanting to enable your class to be usable with ranges, just implement an operator with the name "rangeTo" and you’ll be fine. If you already implemented the 可比 interface, this isn’t even necessary, because Kotlin provides a generic extension to 全部 comparable classes:

可比较的rangeto延伸
operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>

As an example, let’s see how Fraction can be used with ranges.

范围
val fracRange = Fraction(1, 5)..Fraction(5, 7)
println(Fraction(3, 5) in fracRange)
>> prints "true"

我们早些时候看到, Fraction 是-A. 可比 类,使我们能够在框中使用范围。
一个复杂的任务是制作一个课程 迭代 over ranges. This means we would like to use a range of fractions inside a for-loop. In Kotlin, 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: Kotlin defines an extension method to it complying with the conventions.

Let’s see, how we can make Fraction iterable. As we know from the example 可比较的rangeto延伸, using the rangeTo syntax is going to create an instance of 关闭dRange<Fraction>, which, by default, doesn’t have an iterator() function. I’ll show you how to create this method:

迭代器扩展
operator fun ClosedRange<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 and create an implementation of Iterator<Fraction> which is produced by 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 🙂

毁灭声明

另一个kotlin功能似乎 魔法, 一开始,是 毁灭声明。它经常使用for-loops中的maps呈现,将地图的条目销毁到键→值对中。看看这个:

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 by calls to functions with the name component1, component2, componentN. But how does a Map declare those functions? Of course, Kotlin’s stdlib. just adds extensions to Map to enable this functionality.

地图扩展名
public inline operator fun <K, V> Map.Entry<K, V>.component1(): K = key
public inline operator fun <K, V> Map.Entry<K, V>.component2(): V = value

The function component1 simply returns the 钥匙 whereas component2 returns the 价值, really easy once again. These functions are created automatically for every data class by 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 在主构造函数中声明的属性 of a data class.

到目前为止,我们观察到最有趣的Kotlin公约是算术和比较运算符,集合和范围运算符,最后但并非最不重要的销毁声明。我们需要考虑的最后一个公约是 调用公约。我猜,没有多少用例,但一个重要的是 DSL.s。下一章将提供相关信息。

(代表团 也依靠约定,但这个话题值得一个单独的帖子。如果您有兴趣,请看看 文件也许你可以发现该惯例)。

邀请

当我已经在我的最后一篇文章中暗示了 带接收器的功能文字,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 numerator: Int, val denominator: Int) : Comparable<Fraction> {
    //...
    operator fun invoke(prefix: String = "") = println(prefix + toString())

}

在 this invoke operator function a single parameter of type String is defined. The parameter is used to prefix the toString() representation of a Fraction instance and print it to the console. So, how do we use this functionality?

var f = Fraction(2, 3)
f("My invoke prefix: ")
>> prints "My invoke 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 invoke prefix: "). You’re free to choose any signature for your invoke methods, it’s also possible to return values from it.

你知道,Kotlin如何提供 功能类型 以比Java更好的方式吗?看一下 函数.Kt.:当你使用时 lambda.s in your Kotlin code, they are compiled to Function instances, which define 邀请 methods. The following code demonstrates it:

val sum = { x: Int, y: Int -> x + y }
sum.invoke(3, 10)
sum(3, 10)

We define a simple function for adding two 在 t values. This function can be called in two ways: by just putting the parameters in parentheses after the function name, or by 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 Kotlin’s magic.
As for DSLs, the reason for using 邀请 is almost the same. It allows providing 灵活的 ways to use an API.

结论

在本文中,我打算向您展示Kotlin如何利用用于特殊功能的命名约定,这允许我们将运营商过载,将综合值为单独变量过载,使用适当的功能类型或使用范围。理解这些概念在我看来,它在我看来非常重要 许多 explanations to some mysteries of Kotlin 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 keywords in Kotlin 😉

如果您想查看我的示例,则代码可用 GitHub. 存储库。随意,给出任何反馈,我总是很乐意提供帮助。此外,如果你愿意,请看看我的 推特 如果您对更多Kotlin的东西感兴趣,请遵循账户,谢谢。

如果您想了解更多关于Kotlin的美丽功能,我推荐这本书 kotlin在行动中 对你而且还要指导你到我的 其他文章 🙂

 


 

8 thoughts on “kotlin.操作员重载–公约致力于努力

发表评论

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