Defining and calling f 3 unctions
TOP-LEVEL PROPERTIES
3.3 Adding methods to other people’s classes: extension functions and properties
One of the main themes of Kotlin is smooth integration with existing code. Even pure Kotlin projects are built on top of Java libraries such as the JDK, the Android framework, and other third-party frameworks. And when you integrate Kotlin into a Java project, you’re also dealing with the existing code that hasn’t been or won’t be converted to Kotlin. Wouldn’t it be nice to be able to use all the niceties of Kotlin when working with those APIs, without having to rewrite them? That’s what the extension functions allow you to do.
Conceptually, an extension function is a simple thing: it’s a function that can be called as a member of a class but is defined outside of it. To demonstrate that, let’s add a method for computing the last character of a string:
As you can see, all you need to do is put the name of the class or interface that you’re extending before the name of the function you’re adding. This class name is called the receiver type, and the value on which you’re calling the extension function is called the
val UNIX_LINE_SEPARATOR = "\n"
const val UNIX_LINE_SEPARATOR = "\n"
/* Java */
public static final String UNIX_LINE_SEPARATOR = "\n";
package strings
fun String.lastChar(): Char = this.get(this.length - 1)
receiver object. This is illustrated in figure 3.1.
Figure 3.1 The receiver type is the type on which the extension is defined, and the receiver object is the instance of that type.
You can call the function using the same syntax you use for ordinary class members:
In this example,String is the receiver type, and "Kotlin" is the receiver object.
In a sense, you’ve added your own method to theString class. Even though String
isn’t part of your code, and you may not even have the source code to that class, you can still extend it with the methods you need in your project. It doesn’t even matter whether
String is written in Java, Kotlin, or some other JVM language, such as Groovy. As long as it’s compiled to a Java class, you can add your own extensions to that class.
In the body of an extension function, you usethis as you’d use it in a method. And, as in a regular method, you can omit it:
"this" references are implicit.
In the extension function, you can directly access the methods and properties of the class you’re extending, as in methods defined in the class itself. Note that extension functions don’t allow you to break encapsulation. Unlike methods defined in the class, extension functions don’t have access to private or protected members of the class.
3.3.1 Imports and extension functions
When you define an extension function, it doesn’t automatically become available across your entire project. Instead, it needs to be imported, just like any other class or function.
This helps avoid accidental name conflicts. Kotlin allows you to import individual functions using the same syntax you use for classes:
>>> println("Kotlin".lastChar()) n
package strings
fun String.lastChar(): Char = get(length - 1)
import strings.lastChar val c = "Kotlin".lastChar()
Of course,*imports work as well:
You can change the name of the class or function you’re importing using the as
keyword:
Changing a name on import is useful when you have several functions with the same name in different packages and you want to use them in the same file. For regular classes or functions, you have another choice in this situation: you can use a fully qualified name to refer to the class or function. For extension functions, the syntax requires you to use the short name, so the as keyword in an import statement is the only way to resolve the conflict.
3.3.2 Calling extension functions from Java
Calling an extension function doesn’t involve creating adapter objects or any other runtime overhead. Under the hood, an extension function is a static method that accepts the receiver object as its first argument.
That makes using extension functions from Java pretty easy: you call the static method and pass the receiver object instance. Just as with other top-level functions, the name of the Java class containing the method is determined from the name of the file where the function is declared. Let’s say it was declared in a StringUtil.kt file:
This extension function is declared as a top-level function, so it’s compiled to a static method. You can import the lastChar method statically from Java, simplifying the usage to justlastChar("Java"). This code is somewhat less readable than the Kotlin version, but it’s idiomatic from the Java point of view.
3.3.3 Utility functions as extensions
Now you can write the final version of the joinToString function. This is almost exactly what you’ll find in the Kotlin standard library:
import strings.*
val c = "Kotlin".lastChar()
import strings.lastChar as last val c = "Kotlin".last()
/* Java */
char c = StringUtilKt.lastChar("Java");
fun <T> Collection<T>.joinToString(
separator: String = ", ", prefix: String = "", postfix: String = ""
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) { if (index > 0)
result.append(separator) result.append(element) }
result.append(postfix) return result.toString() }
Declares an extension function on Collection<T>
Assigns default values for arguments
"this" refers to the receiver object: a collection of T.
You make it an extension to a collection of elements, and you provide default values for all the arguments. Now you can invokejoinToString like a member of a class:
Because extension functions are effectively just syntactic sugar over static method calls, you can use a more specific type as a receiver type, not just a class. Let’s say you want to have a join function that can be invoked only on collections of strings. Calling this function with a list of objects of another type shouldn’t work:
The static nature of extensions also means extension functions can’t be overridden in subclasses. Let’s look at an example.
>>> val list = arrayListOf(1, 2, 3)
>>> println(list.joinToString(" ")) 1 2 3
fun Collection<String>.join(
separator: String = ", ", prefix: String = "", postfix: String = ""
) = joinToString(separator, prefix, postfix)
>>> println(listOf("one", "two", "eight").join(" ")) one two eight
>!> listOf(1, 2, 8).join()
Error: Type mismatch: inferred type is List<Int> but Collection<String>
was expected.
3.3.4 No overriding for extension functions
Method overriding in Kotlin works as usual for member functions, but you can’t override an extension function. Let’s say you have two classes,View and its subclassButton, and theButtonclass overrides theclickfunction from the superclass:
Button extends View
If you declare a variable of typeView, you can store a value of typeButton in that variable, because Button is a subtype of View. If you call a regular method on this variable, such as click(), and that method is overridden in the Button class, the overridden implementation from theButtonclass will be used:
A method from a Button class of the actual value is called. //AU: Is this wording correct? It seems a little off. TT
But it doesn’t work that way for extensions, as shown in figure 3.2.
Figure 3.2 Extension functions are declared outside of the class.
Extension functions aren’t a part of the class; they’re declared externally to it. Even though you can define extension functions with the same name and parameter types for a
open class View {
open fun click() = println("View clicked") }
class Button: View() {
override fun click() = println("Button clicked") }
>>> val view: View = Button()
>>> view.click() Button clicked
base class and its subclass, the function that’s called depends on the static type of the variable being declared, not on the runtime type of the value stored in that variable.
The following example shows twoshowOff extension functions declared on theView
andButtonclasses:
The extension function is resolved statically.
When you call showOff on a variable of type View, the corresponding extension is called, even though the actual type of the value isButton.
If you recall that an extension function is compiled to a static function in Java with the receiver as the first argument, this behavior should be clear to you, because Java chooses the function the same way:
showOff functions are declared in the extensions.kt file.
As you can see, overriding doesn’t apply to extension functions: Kotlin resolves them statically.
We’ve discussed how to provide additional methods for external classes. Now let’s see how to do the same with properties.
fun View.showOff() = println("I'm a view!") fun Button.showOff() = println("I'm a button!")
>>> val view: View = Button()
>>> view.showOff() I'm a view!
/* Java */
>>> View view = new Button();
>>> ExtensionsKt.showOff(view);
I'm a view!
NOTE Note
If the class has a member function with the same signature as an extension function, the member function always takes precedence.
You should keep this in mind when extending the API of classes: if you add a member function with the same signature as an extension function that a client of your class has defined, and they then recompile their code, it will change its meaning and start referring to the new member function.
3.3.5 Extension properties
Extension properties provide a way to extend classes with APIs that can be accessed using the property syntax, rather than the function syntax. Even though they’re called properties, they can’t have any state: it’s not possible to add extra fields to existing instances of Java objects. But the shorter syntax is still sometimes handy.
In the previous section, you defined a function lastChar. Now let’s convert it into a property:
You can see that, just as with functions, an extension property looks like a regular property with a receiver type added. The getter must always be defined, because there’s no backing field and therefore no default getter implementation. Initializers aren’t allowed for the same reason: there’s nowhere to store the value specified as the initializer.
If you define the same property on aStringBuilder, you can make it avar, because the contents of aStringBuildercan be modified:
Property getter Property setter
You access extension properties exactly like member properties:
Note that when you need to access an extension property from Java, you should invoke its getter explicitly:StringUtilKt.getLastChar("Java").
We’ve discussed the concept of extensions in general. Now let’s return to the topic of collections and look at a few more library functions that help you handle them, as well as language features that come up in those functions.
val String.lastChar: Char get() = get(length - 1)
var StringBuilder.lastChar: Char get() = get(length - 1) set(value: Char) {
this.setCharAt(length - 1, value) }
>>> println("Kotlin".lastChar) n>>> val sb = StringBuilder("Kotlin?")
>>> sb.lastChar = '!'
>>> println(sb) Kotlin!