Explanation of Operator Overloading for Kotlin Learning Tutorial

  • 2021-08-17 01:13:23
  • OfStack

Preface

In Kotlin, we can use conventional operators instead of calling a function defined by a specific name in the code to realize the corresponding operation. For example, if you define a special method named plus in your class, you can use the addition operator + instead of the method call of plus (). Since you can't modify the existing interface definition, 1 can add new convention methods to existing classes by extending functions, so that the operator overloads this 1 syntax sugar to adapt to any existing Java classes.

Arithmetic operator

Let's start with the simplest and most direct example + this kind of arithmetic operator.


data class Point(val x: Int, val y: Int) {
 operator fun plus(other: Point) = Point(x + other.x, y + other.y)
 operator fun plus(value: Int) = "toString: ${Point(x + value, y + value)}"
}

fun main(args: Array<String>) {
 val p1 = Point(1, 2)
 val p2 = Point(3, 4)
 println(p1 + p2)
 println(p1 + 3)
}

/*
Point(x=4, y=6)
toString: Point(x=4, y=5)
*/
The operator modifier is required, otherwise plus is just a normal method and cannot be called through +. Operator has a priority. Compare * has a higher priority than +, and this priority is fixed no matter what object this operator applies to. The plus method has arbitrary parameter types and can therefore be method overloaded, but the number of parameters can only be 1, because + is a 2-ary operator. The return value type of the plus method is also arbitrary. If there are multiple operator extension methods with the same method signature, decide which 1 to use according to import, for example:

//  No. 1 1 Files: 
package package0
operator fun Point.times(value: Int) = Point(x * value, y * value)
//  No. 1 2 Files: 
package package1
operator fun Point.times(value: Int) = Unit // Do nothing.
//  Use the 1 Extension operators: 
import package0.times
val newPoint = Point(1, 2) * 3

Kotlin predefines one operator method for one basic type, and the basic data calculation we usually write can also be translated into calling these operator methods. For example, (2 + 3) * 4 can be translated into 2. plus (3). times (4), and 2 + 3 * 4 can be translated into 2. plus (3. times (4). According to the syntax of the extension function, the extension function cannot override a method with the same signature as the existing method of the class, so there is no need to worry about making 1 + 1 not equal to 2 by simply customizing an plus extension method for Int.

At the same time, all operators are optimized for basic types, such as 1 + 2 * 3, 4 < 5. No function call overhead is introduced for them.

All arithmetic operators that can be overloaded are:

表达式  翻译为
a + b a.plus(b)
a - b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b)、 a.mod(b) (在 Kotlin 1.1 中被弃用)
a..b a.rangeTo(b)

They have the same priority as ordinary numeric type operators. rangeTo is described below.

Generalized assignment operator

表达式  翻译为
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b)、 a.modAssign(b) (在 Kotlin 1.1 中被弃用)

For the above generalized assignment operator:

If the corresponding 2-ary arithmetic operator function is also available, an error is reported. plus corresponds to plusAssign. minus, times, etc. are similar. The return value type must be Unit. If plusAssign does not exist when a + = b is executed, an attempt is made to generate a = a + b, where a + b uses the plus operator method, which is equivalent to calling a = a. plus (b). At this time, the return value type of plus method of a + b must be the same as a type 1 (this is not required if a + b is used alone).

data class Size(var width: Int = 0, var height: Int = 0) {
 operator fun plus(other: Size): Size {
 return Size(width + other.width, height + other.height)
 }
 operator fun plusAssign(other: Size) {
 width += other.width
 height += other.height
 }
}

fun main(args: Array<String>) {
// var s1 = Size(1, 2) //  If so, execute  +=  Errors will be reported when .
 val s1 = Size(1, 2)
 val s2 = Size(3, 4)
 s1 += s2
}

We use this example to understand: Why does s1 defined with var cause += to report an error? In theory, when executing +=, you can call either s1 = s1 + s2, that is, s1 = s1. plus (s2) or s1. plusAssign (s2), both of which conform to the operator overloading convention, which will cause ambiguity. If s1 is defined by val, it is only possible to execute s1. ES90s2, because s1 cannot be reassigned, so the syntax of s1 = s1 + s2 is wrong and can never be called, so calling s1 + =

Since the compiler will help me interpret a + = b as a = a + b, does that mean that I only need plus and never need plusAssign? A better practice is:

+ (plus) always returns 1 new object += (plusAssign) is used for content-mutable types that modify their own content.

This is implemented in the Kotlin standard library:


fun main(args: Array<String>) {
 val list = arrayListOf(1, 2)
 list += 3 //  Add an element to its own collection ,  No new objects were created ,  Called is the  add  Method .
 val newList = list + 4 //  Create 1 A new one  ArrayList,  Adds its own element and a new element and returns a new  ArrayList.
}

in

表达式  翻译为
a in b b.contains(a)
a !in b !b.contains(a)


println("hello" in arrayListOf("hello", ", ", "world"))
/*
true
*/

Using the in operator in the for loop performs an iterative operation, and for (x in list) {/* traversal */} is converted into a call to list. iterator (), and then the hasNext and next methods are repeatedly called above.

rangeTo

rangeTo is used to create 1 interval. For example, 1. 10 is 1. rangeTo (10) for 10 numbers from 1 to 10. The Int. rangeTo method returns an IntRange object. The IntRange class is defined as follows:


/**
 * A range of values of type `Int`.
 */
public class IntRange(start: Int, endInclusive: Int) : IntProgression(start, endInclusive, 1), ClosedRange<Int> {
 override val start: Int get() = first
 override val endInclusive: Int get() = last
 override fun contains(value: Int): Boolean = first <= value && value <= last
 override fun isEmpty(): Boolean = first > last
 override fun equals(other: Any?): Boolean =
 other is IntRange && (isEmpty() && other.isEmpty() ||
 first == other.first && last == other.last)
 override fun hashCode(): Int =
 if (isEmpty()) -1 else (31 * first + last)
 override fun toString(): String = "$first..$last"
 companion object {
 /** An empty range of values of type Int. */
 public val EMPTY: IntRange = IntRange(1, 0)
 }
}

Its base class, IntProgression, implements the Iterable interface, so 1. 10 can be used to iterate:


for (index in 1..10) {
 //  Traversal  1  To  10,  Include  1  And  10.
}

IntRange also implements the interface ClosedRange, which can be used to determine whether an element belongs to the interval.

Kotlin defines the extension function rangeTo for Comparable:


/**
 * Creates a range from this [Comparable] value to the specified [that] value.
 *
 * This value needs to be smaller than [that] value, otherwise the returned range will be empty.
 * @sample samples.ranges.Ranges.rangeFromComparable
 */
public operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T> = ComparableRange(this, that)

Therefore, all Comparable objects can use.. interval operators, such as:


fun main(args: Array<String>) {
 val c1 = Calendar.getInstance() //  On behalf of today .
 val c2 = Calendar.getInstance() 
 c2.add(Calendar.DATE, 10) //  Representative  10  Diva .
 val c3 = Calendar.getInstance()
 c3.add(Calendar.DATE, 3) //  Representative  3  Diva .
 val c4 = Calendar.getInstance()
 c4.add(Calendar.DATE, 13) //  Representative  13  Diva .
 
 //  Determine whether a date is within a range of two dates .
 println(c3 in c1..c2)
 println(c4 in c1..c2)
}
/*
true
false
*/

1 yuan prefix operator

表达式  翻译为
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()


data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus() = Point(-x, -y)
val point = Point(10, 20)
println(-point)
/*
Point(x=-10, y=-20)
*/

Incremental and decreasing

表达式  翻译为
a++ a.inc()
a� a.dec()

The compiler automatically supports the same semantics as the prefix and suffix self-increment operators of ordinary numeric types. For example, the suffix operation returns the value of the variable before performing the + + operation.

Index access operator

表达式  翻译为
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, ……, i_n] a.get(i_1, ……, i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, ……, i_n] = b a.set(i_1, ……, i_n, b)


//  No. 1 1 Files: 
package package0
operator fun Point.times(value: Int) = Point(x * value, y * value)
//  No. 1 2 Files: 
package package1
operator fun Point.times(value: Int) = Unit // Do nothing.
//  Use the 1 Extension operators: 
import package0.times
val newPoint = Point(1, 2) * 3
0

Invoke operator

表达式  翻译为
a() a.invoke()
a(i) a.invoke(i)
a(i, j) a.invoke(i, j)
a(i_1, ……, i_n) a.invoke(i_1, ……, i_n)

Equality and inequality operators

表达式  翻译为
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))

This is defined in Any. a. equals (b) of Java is equivalent to a = = b of Koltin, a = = b is equivalent to a = = = = b of Kotlin (homogeneity check). To customize the = = operator is to override the equals method. In Kotlin = = = cannot be overloaded.

Comparison operator

表达式  翻译为
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

Requires that the compareTo return value type must be Int, which remains 1 with the Comparable interface.


//  No. 1 1 Files: 
package package0
operator fun Point.times(value: Int) = Point(x * value, y * value)
//  No. 1 2 Files: 
package package1
operator fun Point.times(value: Int) = Unit // Do nothing.
//  Use the 1 Extension operators: 
import package0.times
val newPoint = Point(1, 2) * 3
1

The compareValuesBy method is as follows:


//  No. 1 1 Files: 
package package0
operator fun Point.times(value: Int) = Point(x * value, y * value)
//  No. 1 2 Files: 
package package1
operator fun Point.times(value: Int) = Unit // Do nothing.
//  Use the 1 Extension operators: 
import package0.times
val newPoint = Point(1, 2) * 3
2

We define an Movie class, which implements the Comparable interface. When comparing, we want to sort it according to the priority order of rating, release date and movie name. You can simply use the comparison operator to "compare the size" of Movie objects.

Operator Function and Java

Calling the operator method in Kotlin in Java is just like calling ordinary method 1. You can't expect to write the syntax of new Point (1, 2) + new Point (3, 4) in Java, but you can only call new Point (1, 2). plus (new Point (3, 4)).

On the contrary, calling Java code in Kotlin can be as convenient as customizing operator method 1 in Kotlin. As long as a class provides a method that satisfies the operator method signature, even if it is just an ordinary method, it can be called as an operator in Kotlin without adding the operator modifier (this modifier is not found in Java). For example: arrayList [0] is equivalent to arrayList. get (0) in Java, although this get method is defined in Java. For example, all class instances that implement Comparable can use comparison operators > , < And so on.

Bit operators in Java are not available in Kotlin. They can only be used by ordinary methods plus infix expressions, and can only be used for Int and Long. The corresponding relationship is as follows:

Java 中   Kotlin 中
« 有符号左移 shl(bits)
» 有符号右移 shr(bits)
»> 无符号右移 ushr(bits)
& 与 and(bits)
| 或 or(bits)
^ 异或 xor(bits)
! 非 inv()

Operator overload and attribute delegate, infix call

We have also used the operator modifier when using delegate attributes:


class Delegate {
  operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    //...
  }
  operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    //...
  }
}

getValue, setValue conforming to such method signatures are also operator functions, getter and setter for delegating properties.

It can be seen that operator overloading is not a 1. It is necessary to use such as *, +, < Such symbols are used, such as in operator before, getter and setter here.

In addition to these standard overloadable operators, we can also simulate custom infix operators by calling infix functions, and implement syntax such as a in list.

Summarize


Related articles: