As you know, Java has several language features tied to specific classes in the standard library. For example, objects that implementjava.lang.Iterable can be used in for loops, and objects that implement
try-with-resources statements.
java.lang.AutoCloseable can be used in Kotlin has a number of features that work in a similar way, where specific language constructs are implemented by calling functions that you define in your own code. But instead of being tied to specific types, in Kotlin those features are tied to functions with specific names. For example, if your class defines a special method namedplus, then, by convention, you can use the+ operator on instances of this class. Because of that, in Kotlin we refer to this technique as conventions. In this chapter, we’ll look at different conventions supported by Kotlin and how they can be used.
Kotlin uses the principle of conventions, instead of relying on types as Java does, because this allows Kotlin to adapt existing Java classes to the requirements of Kotlin language features. The set of interfaces implemented by a class is fixed, and Kotlin can’t modify an existing class so that it would implement additional interfaces. On the other hand, defining new methods for a class is possible through the mechanism of extension functions. You can define any convention methods as extensions and thereby adapt any existing Java class without modifying its code.
As a running example in this chapter, we’ll use a simple Point class, representing a point on a screen. Such classes are available in most UI frameworks, and you can easily
This chapter covers
7
Operator overloading
Special-named functions supporting various operations Delegated properties
adapt the definitions shown here to your environment:
Let’s begin by defining some arithmetic operators on thePointclass.
7.1 Overloading arithmetic operators
The most straightforward example of the use of conventions in Kotlin is arithmetic operators. In Java, the full set of arithmetic operations can be used only with primitive types, and additionally the + operator can be used with String values. But these operations could be convenient in other cases as well. For example, if you’re working with numbers through theBigInteger class, it’s more elegant to sum them using+ than to call theadd method explicitly. To add an element to a collection, you may want to use the+= operator. Kotlin allows you to do that, and in this section we’ll show you how it works.
7.1.1 Overloading binary arithmetic operations
The first operation you’re going to support is adding two points together. This operation sums up the points' X and Y coordinates. Here’s how you can implement it:
Defines an operator function named "plus"
Adds the coordinates and returns a new point Calls the "plus" function using the + sign
Note how you use theoperator keyword to declare the plus function. All functions used to overload operators need to be marked with that keyword. This makes it explicit that you intend to use the function as an implementation of the corresponding convention and that you didn’t define a function that accidentally had a matching name.
After you declare the plus function with the operator modifier, you can sum up your objects using just the + sign. Under the hood, the plus function is called as shown in figure 7.1.
data class Point(val x: Int, val y: Int)
data class Point(val x: Int, val y: Int) { operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y) } }
>>> val p1 = Point(10, 20)
>>> val p2 = Point(30, 40)
>>> println(p1 + p2) Point(x=40, y=60)
Figure 7.1 The '+' operator is transformed into a plus function call.
As an alternative to declaring the operator as a member, you can define the operator as an extension function:
The implementation is exactly the same. Future examples wil use the extension function syntax because it’s a common pattern to define convention extension functions for external library classes, and the same syntax will work nicely for your own classes as well.
Compared to some other languages, defining and using overloaded operators in Kotlin is simpler, because you can’t define your own operators. Kotlin has a limited set of operators that you can overload, and each one corresponds to the name of the function you need to define in your class. Table 7.1 lists all the binary operators you can define and the corresponding function names.
Table 7.1 Overloadable binary arithmetic operators
Expression Function name
a * b times
a / b div
a % b mod
a + b plus
a - b minus
Operators for your own types always use the same precedence as the standard numeric types. For example, if you write a + b * c, the multiplication will always be executed before the addition, even if you’ve defined those operators yourself. The operators*,/, and% have the same priority, which is higher than the priority of the+and
-operators.
operator fun Point.plus(other: Point): Point { return Point(x + other.x, y + other.y) }
SIDEBAR Operator functions and Java
Kotlin operators are easy to call from Java: because every overloaded operator is defined as a function, you call them as regular functions using the full name.
When you call Java from Kotlin, you can use the operator syntax for any methods with names matching the Kotlin conventions. Because Java doesn’t define any syntax for marking operator functions, the requirement to use the
operator modifier doesn’t apply, and the matching name is the only constraint.
If a Java class defines a method with the behavior you need but gives it a different name, you can define an extension function with the correct name that would delegate to the existing Java method.
When you define an operator, you don’t need to use the same types for the two operands. For example, let’s define an operator that will allow you to scale a point by a certain number. You can use it to translate points between different coordinate systems:
Note that Kotlin operators don’t automatically support commutativity (the ability to swap the left and right sides of an operator). If you want users to be able to write1.5 * p in addition top * 1.5, you need to define a separate operator for that:operator fun Double.times(p: Point).
The return type of an operator function can also be different from either of the operand types. For example, you can define an operator to create a string by repeating a character a number of times:
This operator takes aChar as the left operand and anIntas the right operand and has
String as the result type. Such combinations of operand and result types are perfectly acceptable.
operator fun Point.times(scale: Double): Point {
return Point((x * scale).toInt(), (y * scale).toInt()) }
>>> val p = Point(10, 20)
>>> println(p * 1.5) Point(x=15, y=30)
operator fun Char.times(count: Int): String { return toString().repeat(count) }
>>> println('a' * 3) aaa