Zumry Mohamed
Zumry Mohamed

Reputation: 9558

Kotlin Extension Any?.toString()

I have been trying to use extension functions in Kotlin.

class ExtensionExample() {

   var name: String? = null

   fun Any?.toString() : String {

       if (this == null) {
           return "Value is null"
       }

       return "Value is not null"
   }
}

When I print the name variable like below

println(ExtensionExample().name.toString())

It should print like

Value is null

But it doesn't print like I expected. It only prints null.

Can someone explain please?

Upvotes: 4

Views: 5909

Answers (3)

Salem
Salem

Reputation: 14907

Extension functions have scopes. This is why it is not accessible outside of the ExtensionExample class.

Usually, extension functions are defined as top-level functions. That is, they tend to belong outside of a class.

Your toString extension is defined within ExtensionExample. This is possible, but it means it is only available in the scope of this class, and this class only.

Your main method is likely a top-level function (and not a member of that class), so it doesn't have access to this extension function.

Only other members can access this extension function:

class ExtensionExample {

    var name: String? = null

    fun Any?.toString(): String {
        return if (this == null) "Value is null" else "Value is not null"
    }

    fun foo() {
        println(name.toString())
    }

}

fun main(args: Array<String>) {
    ExtensionExample().foo()
}

prints "Value is null"

Try it online!

The reason why it compiles is that the toString method that is being called is the Any?.toString method from the kotlin-stdlib. That is, this method already exists.

fun Any?.toString(): String

Returns a string representation of the object. Can be called with a null receiver, in which case it returns the string "null".

Move your extension function out of the class. Your extension function will then hide the stdlib's extension, and any invocation in the same package not explicitly importing kotlin.toString will use your function.

Here is a working version of your code:

class ExtensionExample {

    var name: String? = null

}

fun Any?.toString(): String {
    return if (this == null) "Value is null" else "Value is not null"
}

fun main(args: Array<String>) {
    println(ExtensionExample().name.toString())
}

prints "Value is null", as you wanted.

Try it online!


Note that you cannot hide a member function using an extension function. That is,

class Test {

    fun a() {
        println("a")
    }

}

fun Test.a() {
    println("b")
}

...
Test().a()

will result in "a" being printed.

The only reason why your Any?.toString works (after being moved out of the class) is that Any? has no members as it is not a class, but a nullable type with the class Any. The already existing toString method is also an extension, so yours is not hidden by any members.

Upvotes: 8

Willi Mentzel
Willi Mentzel

Reputation: 29844

Defining an extension function inside a class:

If you want to leave the definition of toString() within ExtensionExample, you can only use it from there:

class ExtensionExample() {
    var name: String? = null

    // uses the toString() extension function defined in ExtensionExample
    fun useToString() = name.toString()

    fun Any?.toString(): String {

        if (this == null) {
            return "inside: Value is null"
        }
        return "inside: Value is not null"
    }
}

This

// uses the version of toString() defined in the Kotlin Standard library
println(ExtensionExample().name.toString()) 

// uses the inside version of toString()
println(ExtensionExample().useToString())

will print

null
inside: Value is null

Defining an extension function outside a class:

But if you want to use it outside, you have to move it outside:

class ExtensionExample() {
    var name: String? = null

    // uses the toString() extension function defined in ExtensionExample
    fun useToString() = name.toString()

    fun Any?.toString(): String {

        if (this == null) {
            return "inside: Value is null"
        }
        return "inside: Value is not null"
    }
}

fun Any?.toString(): String {

    if (this == null) {
        return "outside: Value is null"
    }
    return "outside: Value is not null"
}

This

// uses the outside version of toString() which overrides the one from the Kotlin Standard library
println(ExtensionExample().name.toString())  

// uses the inside version of toString()
println(ExtensionExample().useToString())

will print:

outside: Value is null
inside: Value is null

Upvotes: 3

zsmb13
zsmb13

Reputation: 89548

If a class has a member function, and an extension function is defined which has the same receiver type, the same name and is applicable to given arguments, the member always wins.

Your extension method can't shadow the built-in toString method on the Any (Object) class.

As for calling toString on a nullable type, this method is already in the Kotlin runtime:

/**
 * Returns a string representation of the object. Can be called with a null receiver, in which case
 * it returns the string "null".
 */
public fun Any?.toString(): String

See also the docs about the resolution of extensions.

Upvotes: 0

Related Questions