Preface
In Kotlin, we can use the convention operator instead of calling functions defined by a specific name in the code to implement the corresponding operations. For example, if a special method named plus is defined in a class, you can use the addition operator + instead of the method call of plus(). Since you cannot modify the existing interface definition, you can generally add new convention methods to existing classes by extending functions, so that operator overloading is a syntax sugar that adapts to any existing Java class.
Arithmetic operators
Let's start with the simplest and most straightforward example + this type of arithmetic operator.
data class Point(val x: Int, val y: Int) { operator fun plus(other: Point) = Point(x + , 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 with +.
- Operators have priority, and the priority is higher than +. No matter what object this operator is applied to, this priority is fixed.
- The parameter type of the plus method is arbitrary, so it can be overloaded, but the number of parameters can only be 1 because + is a binary operator. The return value type of the plus method is also arbitrary.
- If multiple operator extension methods with the same method signature appear, decide which one to use according to import, for example:
// The first file:package package0 operator fun (value: Int) = Point(x * value, y * value) // The second file:package package1 operator fun (value: Int) = Unit // Do nothing. // Use the first extension operator:import val newPoint = Point(1, 2) * 3
Kotlin predefined some operator methods for some basic types. The basic data calculations we usually write can also be translated into calling these operator methods. For example, (2 + 3) * 4 can be translated into (3).times(4), and 2 + 3 * 4 can be translated into ((4)). According to the syntax of the extension function, the extension function cannot override the same method signature as the class's existing method. Therefore, you don't have to worry about simply customizing a plus extension method to Int to make 1 + 1 not equal to 2.
At the same time, all operators are optimized for basic types, such as 1 + 2 * 3, 4 < 5, and will not introduce the overhead of function calls for them.
All overloadable arithmetic operators are:
Expressions | Translated as |
---|---|
a + b | (b) |
a - b | (b) |
a * b | (b) |
a / b | (b) |
a % b | (b), (b) (deprecated in Kotlin 1.1) |
a..b | (b) |
Their priority is the same as that of ordinary numeric type operators. rangeTo will be explained below.
Generalized assignment operator
Expressions | Translated as |
---|---|
a += b | (b) |
a -= b | (b) |
a *= b | (b) |
a /= b | (b) |
a %= b | (b)、 |
For the above generalized assignment operators:
- If the corresponding binary arithmetic operator function is also available, an error is reported. plus corresponds to plusAssign. Minus, times, etc. are also similar.
- The return value type must be Unit.
- If plusAssign does not exist when executing a += b, it will try to generate a = a + b, where a + b uses the plus operator method, which is equivalent to calling a = (b). And at this time, the return value type of the plus method of a + b must be consistent with the type a (if a + b is not used alone).
data class Size(var width: Int = 0, var height: Int = 0) { operator fun plus(other: Size): Size { return Size(width + , height + ) } operator fun plusAssign(other: Size) { width += height += } } fun main(args: Array<String>) { // var s1 = Size(1, 2) // If you write this, an error will be reported when executing +=. val s1 = Size(1, 2) val s2 = Size(3, 4) s1 += s2 }
Let's use this example to understand: Why does using s1 defined by var cause += to report an error? Because theoretically, when executing +=, you can call both s1 = s1 + s2, that is, s1 = (s2), and (s2), both conform to the operator overloading convention, which will cause ambiguity. If s1 is defined using val, it is only possible to execute (s2), because s1 cannot be reassigned, so syntax such as s1 = s1 + s2 is an error and can never be called, so calling s1 += s2 will not cause ambiguity.
Since the compiler will help me interpret a += b as a = a + b, does that mean I only need plus and never need plusAssign? A better way of practice is:
- + (plus) Always return a new object
- += (plusAssign) is used to modify its own content types.
This is how the Kotlin standard library is implemented:
fun main(args: Array<String>) { val list = arrayListOf(1, 2) list += 3 // Add elements to its own collection, no new objects are created, the add method is called. val newList = list + 4 // Create a new ArrayList, add its own element and new element and return a new ArrayList.}
in
Expressions | Translated as |
---|---|
a in b | (a) |
a !in b | !(a) |
println("hello" in arrayListOf("hello", ", ", "world")) /* true */
Using the in operator in a for loop will perform iterative operations, for(x in list) { /* traversal */ } will be converted into a call of (), and then the hasNext and next methods are repeatedly called on it.
rangeTo
rangeTo is used to create an interval. For example, 1..10 means (10) represents the 10 numbers from 1 to 10. The 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() && () || first == && 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) { // traverse 1 to 10, including 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 */ public operator fun <T: Comparable<T>> (that: T): ClosedRange<T> = ComparableRange(this, that)
Therefore, all Comparable objects can use the .. interval operator, for example:
fun main(args: Array<String>) { val c1 = () // Represents today. val c2 = () (, 10) // Represents 10 days later. val c3 = () (, 3) // Represents 3 days old. val c4 = () (, 13) // Represents 13 days old. // Determine whether a date is within the range of two dates. println(c3 in c1..c2) println(c4 in c1..c2) } /* true false */
unary prefix operator
Expressions | Translated as |
---|---|
+a | () |
-a | () |
!a | () |
data class Point(val x: Int, val y: Int) operator fun () = Point(-x, -y) val point = Point(10, 20) println(-point) /* Point(x=-10, y=-20) */
Increment and Decrease
Expressions | Translated as |
---|---|
a++ | () |
a– | () |
The compiler automatically supports the same semantics as the prefix and suffix autoincrement operators of ordinary numeric types. For example, the suffix operation will return the value of the variable first, and then perform the ++ operation.
Index access operator
Expressions | Translated as |
---|---|
a[i] | (i) |
a[i, j] | (i, j) |
a[i_1, ……, i_n] | (i_1, ……, i_n) |
a[i] = b | (i, b) |
a[i, j] = b | (i, j, b) |
a[i_1, ……, i_n] = b | (i_1, ……, i_n, b) |
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST") operator fun <T> (key: String, defValue: T) = when (defValue) { is String -> getString(key, defValue) is Int -> getInt(key, defValue) is Long -> getLong(key, defValue) is Float -> getFloat(key, defValue) is Boolean -> getBoolean(key, defValue) else -> throw RuntimeException() } as T @SuppressLint("CommitPrefEdits") operator fun <T> (key: String, value: T) = with(edit()) { when (value) { is String -> putString(key, value) is Int -> putInt(key, value) is Long -> putLong(key, value) is Float -> putFloat(key, value) is Boolean -> putBoolean(key, value) else -> throw RuntimeException() }.apply() } fun main(args: Array<String>) { val version = sp["key_version", 47] // Read sp. sp["key_version"] = 48 // Write sp.}
Call operator
Expressions | Translated as |
---|---|
a() | () |
a(i) | (i) |
a(i, j) | (i, j) |
a(i_1, ……, i_n) | (i_1, ……, i_n) |
Equal and unequal operators
Expressions | Translated as |
---|---|
a == b | a?.equals(b) ?: (b === null) |
a != b | !(a?.equals(b) ?: (b === null)) |
This is defined in Any. Java's (b) is equivalent to Koltin's a == b, and Java's a == b is equivalent to Kotlin's a === b (identity check). To customize the == operator, it is actually overriding the equals method. === cannot be overloaded in Kotlin.
Comparison operator
Expressions | Translated as |
---|---|
a > b | (b) > 0 |
a < b | (b) < 0 |
a >= b | (b) >= 0 |
a <= b | (b) <= 0 |
Requires that the compareTo return value type must be Int , which is consistent with the Comparable interface.
data class Movie(val name: String, val score: Int, val date: Date, val other: Any = Any()) : Comparable<Movie> { override fun compareTo(other: Movie): Int { return compareValuesBy(this, other, Movie::score, Movie::date, Movie::name) // If Movie::other is also used as a comparison, it will cause an error because other is not of Comparable type. } } fun main(args: Array<String>) { val df = SimpleDateFormat("yyyy-MM-dd", ()) val movie0 = Movie("The King of Circus", 8, ("2018-01-31")) val movie1 = Movie("Mysterious Superstar", 7, ("2018-01-01")) val movie2 = Movie("Mobile Maze", 7, ("2018-01-02")) println(movie0 < movie1) println(movie1 < movie2) } /* false true */
The compareValuesBy method is as follows:
/** * Compares two values using the specified functions [selectors] to calculate the result of the comparison. * The functions are called sequentially, receive the given values [a] and [b] and return [Comparable] * objects. As soon as the [Comparable] instances returned by a function for [a] and [b] values do not * compare as equal, the result of that comparison is returned. * * @sample */ public fun <T> compareValuesBy(a: T, b: T, vararg selectors: (T) -> Comparable<*>?): Int { require( > 0) return compareValuesByImpl(a, b, selectors) } private fun <T> compareValuesByImpl(a: T, b: T, selectors: Array<out (T)->Comparable<*>?>): Int { for (fn in selectors) { val v1 = fn(a) val v2 = fn(b) val diff = compareValues(v1, v2) if (diff != 0) return diff } return 0 }
We define a Movie class that implements the Comparable interface. When comparing, we want to sort in priority order of ratings, release dates, and movie names. You can simply use the comparison operator to "size comparison" of Movie objects.
Operator functions and Java
Calling the operator method in Kotlin in Java is just like calling ordinary methods. You cannot expect to write syntax such as new Point(1, 2) + new Point(3, 4) in Java. 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 custom operator methods in Kotlin. As long as a class provides a method that satisfies the operator method signature, even if it is just a normal method, without adding an operator modifier (there is no such modifier in Java), it can be called in Kotlin as an operator. For example: arrayList[0] is equivalent to (0) in Java, although this get method is defined in Java. For example, all class instances that implement Comparable can be compared using comparison operators >, <, etc.
The bit operators in Java are not available in Kotlin. They can only be used with ordinary methods plus infix expressions, and can only be used for Int and Long. The corresponding relationship is as follows:
In Java | Kotlin |
---|---|
« Signed left | shl(bits) |
» Signed right | shr(bits) |
»> Unsigned right | ushr(bits) |
& and | and(bits) |
| or | or(bits) |
^ Exclusive or | xor(bits) |
! No | inv() |
Operator overloading, attribute delegate, infix call
We also used the operator modifier when using the delegate attribute:
class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { //... } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { //... } }
GetValue and setValue that conform to the signature of such method are also operator functions, used for getters and setters of delegate properties.
It can be seen that operator overloading does not necessarily mean symbols such as *, +, and <, such as the previous in operator, getter and setter here.
In addition to the above standard overloadable operators, we can also simulate custom infix operators through the call of the infix function to implement syntax such as a in list.
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.