Friday 16 July 2021

Android Java to Kotlin migration by example part 9: extension functions

One of Kotlin features which are not available in Java are extension functions. These are the functions which can be used as methods with existing class objects, but are not defined in them. You may already know them if you work with C# - they've been there for some time now.

It's quite useful, when we want to, for instance, define a method we would like to use with String type, but it's not defined by default. Of course, we could inherit from String, but it would mean we have to use the subclass everywhere when we could possibly want to use out new method.

Other solution is to simply create a function, getting our String as param. This is the easiest way this could be done in languages without extension functions (like Java). Let's consider the following function, counting uppercase characters in a string given as its param and returning the result:

Listing 1 (Java)

import static java.lang.System.out;

public class MainClass {

static int howManyUppercase(String string){
int res =0;
for(int i=0;i<string.length();i++)
if(Character.isUpperCase(string.charAt(i)))
res++;
return res;
}

public static void main(String[] args){
String string ="SoMe WoRdS";
out.println(howManyUppercase(string));
}
}

In Kotlin we could write very similar code. But we can also do this:

Listing 2 (Kotlin)

package com.mypackage

fun String.howManyUppercase(): Int{
var res = 0
for(i in 0..this.length-1)
if(this[i].isUpperCase())
res++
return res
}

fun main() {
var string = "SoMe WoRdS"
println(string.howManyUppercase())
}

We define our extension function as we would declare a normal function, but its name is preceded with extended class (in our case that's String ) and a dot.

Our howManyUppercase() becomes basically a String method. It's called like one, and is suggested by the IDE autocompletion.

It's important to remember that extensions are available only in the package they're defined (which makes sense, as it prevents possible conflicts), so it you want to use it outside of that package, the extension (or all the package contents) has to be imported, as shown below (the version with whole package import is commented out):

Listing 3 (Kotlin import)

package com.mypackage2 //!

import com.mypackage.howManyUppercase
// import com.mypackage.* // alternative

class AnotherClass {
fun myStringSomething(): Int{
return "blabla".howManyUppercase()
}
}

The extension functions can "extend" any class - although it seem to make more sense to use them with the standard defined types (as String), which source is not directly available (they're already compiled), you can also extend your own classes.

Please note that as extension function can extend any class, they can also extend Any class :)

In that case, the function can be called for basically every object in the scope of extension function:

package com.mypackage

import java.util.*

fun Any.howManyUppercase(): Int{
if(this is String) {
var res = 0
for(i in 0..this.length-1)
if(this[i].isUpperCase())
res++
return res
}
else
return(-1)
}

fun main() {
var string = "SoMe WoRdS"
println(string.howManyUppercase())
var someDate = Date()
println(someDate.howManyUppercase())
var num: Int = 2
println(num.howManyUppercase())
}

This would return -1 for the objects which are not String. We're using is operator and smart casting, mentioned in the previous post. And although the example above seem to not have much sense (what's the point of extending Any, if de facto only extended type is String?), we can consider the following case:

package com.mypackage

import java.util.*


fun Any.howManyUppercase(): Int{
when (this) {
is String -> {
var res = 0
for(i in 0..this.length-1)
if(this[i].isUpperCase())
res++
return res
}
is CharArray -> {
var res = 0
for(i in 0..this.size-1)
if(this[i].isUpperCase())
res++
return res
}
else -> return(-1)
}
}

fun main() {
var string = "SoMe WoRdS"
println(string.howManyUppercase())
var someDate = Date()
println(someDate.howManyUppercase())
var num: Int = 2
println(num.howManyUppercase())

var arr = string.toCharArray()
println(arr.howManyUppercase())
}

Now if we apply our extension to String or CharArray, it returns what we want. In any other case, it returns -1. If we had some more complex task, it's possible we would like to extend more types this way (using only one method).

No comments:

Post a Comment

Python crash course part 10: inheritance and polymorphism

In the last part we've shown how to create and use a class in Python. Today we're going to talk about inheritance: wchich means cre...