Extension function and operator overloading
While many modern high-level programming languages have the concept of extension functions, It’s a shame that Java hasn’t supported this very useful feature. The good news is that Kotlin has excellent support for extension functions, so this is something that we shouldn’t miss for the world.
A useful extension function
Let’s first look at what an extension function is. An extension function means that you can open a class and add new functions to it without modifying the source code of the class.
To help you understand this better, let’s think about a feature. A string may contain characters such as letters, numbers, and special symbols. Now we want to count the number of letters in the string. How do you do this? If you follow normal programming thinking, perhaps most people would naturally write the following function:
object StringUtil {
fun letterCount(str: String) : Int {
var count = 0
for (char in str) {
if (char.isLetter()) {
count ++
}
}
return count
}
}
Copy the code
A StringUtil singleton class is defined, and within that singleton a lettersCount() function is defined that takes a string argument. In the lettersCount() method, we use a for-in loop to iterate over each character in the string. If the character is a letter, the counter is incremented by 1, and the counter’s value is returned.
Now, when we need to count the number of letters in a string, we just need to write the following code:
val str = "ABC123xyz! @ #"
val count = StringUtil.lettersCount(str)
Copy the code
It definitely works, and it’s the most standard implementation thinking in Java programming. But with extension functions, we can use a more object-oriented approach, such as adding the lettersCount() function to the String class.
Let’s look at the syntax for defining an extension function. It’s very simple, as follows:
fun ClassName.methodName(param1: Int, param2: Int): Int {
return 0
}
Copy the code
Instead of defining a normal function, define an extension function by adding a ClassName in front of the function name. Is added to the specified class.
Now that we know the syntax for defining an extension function, let’s try to optimize the statistics function using an extension function.
Since we want to add an extension function to the String class, we need to create a string.kt file first. The name of the file is not fixed, but I recommend defining a Kotlin file with the same name as the class you add the extension function to, so you can find it later. Of course, extension functions can be defined in any existing class without creating a new file. In general, however, it is best to define it as a top-level method, which gives the extension function global access.
Now write the following code in the string.k t file:
fun String.lettersCount(a): Int {
var count = 0
for (char in this) {
if (char.isLetter()) {
count++
}
}
return count
}
Copy the code
Notice the code change here. We now define the lettersCount() method as an extension function of the String class, so we automatically have the context of the String instance in the function. So instead of taking a string argument, the lettersCount() function simply iterates over this, which now represents the string itself.
After the extension function is defined, counting the number of letters in a string can be done as follows:
val count = "ABC123xyz! @ #".lettersCount()
Copy the code
Isn’t that amazing? It looks as if the String class has its own lettersCount() method.
Extension functions can in many cases make an API cleaner, richer, and more object-oriented. Let’s take the String class again. It’s a final class, and no other class can inherit from it, which means it has a fixed API, at least in Java. In Kotlin, however, we can extend any function to the String class to enrich its API. For example, you’ll find that String in Kotlin even has the reverse() function to reverse a String, the capitalize() function to capitalize the first letter, and so on, these are some of the extension functions that come with Kotlin. This feature makes our programming easier.
Also, don’t be limited by the examples in this section. You can add extension functions to any class other than String, and Kotlin has virtually no restrictions on this. If you take advantage of the extension function, it will greatly improve the quality of your code and the efficiency of your development.
Interesting operator overloading
Operator overloading is one of the more interesting syntactic candies that Kotlin provides. As we know, Java has many of the language’s built-in operator keywords, such as + – * / % ++ –. Kotlin allows us to extend the use of all operators and even other keywords by overloading them.
This section is a little more complicated than Kotlin, but I promise you, it’s a very interesting section, and you’ll get a lot more out of it.
Let’s first review the basic use of operators. I believe everyone has used the four operators of addition, subtraction, multiplication and division. In programming languages, adding two numbers means finding the sum of two numbers, and adding two strings means concatenating two strings. This basic usage is understood by anyone who has been in programming. But Kotlin’s operator overloading allows us to add any two objects, or any number of other operations.
Of course, while Kotlin has given us this ability, it’s important to consider the logic when actually programming. For example, adding two Student objects doesn’t seem to make sense, but adding two Money objects does, because Money can be added.
So let’s first look at the basic syntax of operator overloading, and then implement the ability to add two Money objects.
Operator overloading uses the operator keyword, which can be implemented by prefixing the specified function with the operator keyword. But the question is what is the specified function? This is one of the more complicated aspects of operator overloading, because different operators have different overloaded functions. For example, the plus operator corresponds to the plus() function and the minus() operator corresponds to the minus() function.
Let’s use the plus operator again. If we wanted to add two objects, the syntax would look like this:
class obj {
operator fun plus(obj: Obj):Obj {
// Handle the logic of addition}}Copy the code
In the syntax above, the keyword operator and the function name plus are fixed, while the arguments received and the return value of the function can be set according to your logic. This code then indicates that an Obj object can be added to another Obj object to return a new Obj object. The corresponding invocation mode is as follows:
val obj1 = Obj()
val obj2 = Obj()
val obj3 = obj1 + obj2
Copy the code
This obj1 + obj2 syntax may seem like magic, but it’s actually a syntactic sugar that Kotlin is giving us, which is converted to obj1.plus(obj2) at compile time.
With the basic syntax of operator overloading in mind, let’s move on to a more meaningful feature: adding two Money objects.
To begin by defining the structure of the Money class, I’m going to have the main constructor of Money take a value parameter that represents the amount of Money. Create the money. kt file with the following code:
class Money(val value: Int)
Copy the code
Having defined the structure of the Money class, we use operator overloading to add two Money objects:
class Money(val value: Int) {
operator fun plus(money: Money): Money {
val sum = value + money.value
return Money(sum)
}
}
Copy the code
As you can see, the operator keyword is used to decorate the plus() function, which is essential. In the plus() function, we add the value of the current Money object to the value of the Money object passed in as a parameter, then pass the resulting sum to a new Money object and return that object. Then the two Money objects can be added. It’s as simple as that.
Now we can test the functionality we just wrote with the following code:
val money1 = Money(5)
val money2 = Money(10)
val money3 = money1 + money2
println(money3.value)
Copy the code
However, Money objects are only allowed to be added to another Money object. You might think it would be better if Money objects could be added directly to numbers. This is possible, of course, because Kotlin allows us to overload the same operator multiple times, as shown in this code:
class Money(val value: Int) {
operator fun plus(money: Money): Money {
val sum = value + money.value
return Money(sum)
}
operator fun plus(newValue: Int): Money {
val sum = value + newValue
return Money(sum)
}
}
Copy the code
Here we’re overloading the plus() function again, but this time it takes an integer number, and the rest of the code is basically the same.
So now, the Money object has the ability to add to numbers:
val money1 = Money(5)
val money2 = Money(10)
val money3 = money1 + money2
val money4 = money3 + 20
println(money4.value)
Copy the code
Add the money3 object to the amount of 20, and the final print is 35.
Of course, you can extend this example to include the ability to convert exchange rates. Add the 1 RMB Money object to the 1 US dollar Money object, and then convert it based on the real-time exchange rate to return a new Money object. These types of features are very interesting, and operator overloading can play a lot of tricks when used well.
We’ve spent a lot of time talking about the use of plus operator overloading, but Kotlin actually allows us to overload a dozen operators and keywords. Obviously, I can’t cover every use of overloading here, so I’ve listed the syntactic sugar expressions for all the common overloadable operators and keywords in the table below, and the actual calling functions that they translate into. If you want to overload one of these operators or keywords, just refer to the way we just wrote the plus operator overload.
Syntactic sugar expression | Actually calling the function |
---|---|
a+b | a.plus(b) |
a-b | a.plus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) |
a++ | a.inc() |
a– | a.dec() |
+a | a.unaryPlus() |
-a | a.unaryMinus() |
! a | a.not() |
a == b | a.equals(b) |
a > b | a.equals(b) |
a < b | a.equals(b) |
a >= b | a.equals(b) |
a <= b | a.compareTo(b) |
a.. b | a.rangeTo(b) |
a[b] | a.get(b) |
a[b] = c | a.set(b, c) |
a in b | b.contains(a) |
So much for operator overloading. Next, let’s combine what we just learned about extension functions and operator overloading to optimize a small feature we wrote earlier.
Recall that in both Chapter 4 and this chapter, we used a function that randomly generates the length of a string. The code looks like this:
fun getRandomLengthString(str: String): String {
val n = (1.20.).random()
val builder = StringBuilder()
repeat(n) {
builder.append(str)
}
return builder.toString()
}
Copy the code
Now, the whole idea of this function is to repeat the string n times. Wouldn’t it be nice if we could write STR * n to say that the STR string is repeated n times? And in Kotlin it’s possible.
So let’s start with the idea. To multiply a String by a number, you’d have to override the multiplication operator in the String class, but String is a system-provided class, and we can’t modify its code. At this point, you can use the extension function function to add new functions to the String class.
Since we are adding the extension function to String, let’s open the string. kt file we created and add the following code:
operator fun String.times(n: Int): String {
val builder = StringBuilder()
repeat(n) {
builder.append(this)}return builder.toString()
}
Copy the code
This code should not be hard to understand, but just a few key points. First, the operator keyword is definitely necessary; Then, to override the multiplier operator, the function name must be times. Finally, since we are defining the extension function, we also add String before the direction name. The grammatical structure of. There’s nothing else to explain. In The Times () function, we use the StringBuilder and repeat functions to repeat the string n times and finally return the result.
Now, strings have the ability to be multiplied by a number, such as by executing the following code:
val str = "abc" * 3
println(str)
Copy the code
The final print is: ABCABCABC.
In addition, it should be noted that Kotlin’s String class already provides a repeat() function for repeating a String n times, so The Times () function can be further reduced to the following form:
operator fun String.times(n: Int) = repeat(n)
Copy the code
With that in mind, we can now use this magic notation in the getRandomLengthString() function as follows:
fun getRandomLengthString(str: String) = str * (1.20.).random()
Copy the code
Well, do you find this grammar particularly comfortable to use? As long as you are flexible with the extension functions and operator overloading learned in this section, you can define more interesting and efficient syntactic structures, which will be expanded in subsequent sections of this book.