Kotlin : function extension inside companion object
Asked Answered
C

2

5

In Kotlin language, what does this syntax do and how does it work ?

class ClassName1 {
    companion object {          
        fun ClassName2.funName()=""         
    } 
}
Caraway answered 7/6, 2022 at 14:22 Comment(2)
I took the liberty to edit the style of the code so it matches Kotlin convention and sort of compiles. Please feel free to correct it if this is not what you had in mindWomble
If this comes from a real-life example, you could share how this extension function is used, so we can explain why it's declared this way. This would also allow us to mention why it couldn't be done differently, or whether it could in fact have been done differently.Womble
W
8

There are multiple things at play here:

  • extension functions: they allow to use the function as if it were part of the receiver type (the type on the left of the function name)
  • extension functions used as members: extension functions that can be used on their receiver (known as extension receiver) only when the enclosing class (the dispatch receiver) is in scope
  • companion objects: a special object that is associated to a class and can be referenced by just using the class name

Declaring a member extension function inside a companion object might be useful for different things.

For instance, such function can be used within the class as if the extension function was declared as a class member but outside the companion. Some people prefer to put them in the companion object to make it clear that they don't depend on the state of said class (like a Java static function):

class ClassName1 {

    fun method(): String {
        val something = ClassName2()
        return something.funName()
    }

    companion object {          
        fun ClassName2.funName() = ""         
    } 
}

Such use doesn't require the function to be public, though.

Another way to use this would be by using the companion object as a sort of scope:

val something = ClassName2()
with(ClassName1) { // this: ClassName1.Companion
   something.funName() // brought in scope by ClassName1's companion
}

Or directly if you import the function from the companion:

import ClassName1.Companion.funName

val something = ClassName2()
something.funName()

Such pattern is used for instance for Duration.Compaion to define extensions on number types (those are extension properties but it's the same idea): https://github.com/JetBrains/kotlin/blob/6a670dc5f38fc73eb01d754d8f7c158ae0176ceb/libraries/stdlib/src/kotlin/time/Duration.kt#L71

Womble answered 7/6, 2022 at 15:8 Comment(7)
and how can I call funName() in the first example ?Caraway
@Joffrey, do you know why they chose to put those duration extension properties in Duration.Companion? I find it very inconvenient because then you must manually type the import statement, remembering where to find them, before you can even use those properties. They deprecated the bare ones in the same file. Maybe to avoid cluttered namespace? But Numbers don't have very many members anyway.Kepi
@Kepi I'm also disappointed by this choice. The reason I think was to allow them to later limit the undesired entries in autocompletion for such common types by improving IDEA using context-aware completion (if you autocomplete in a context that expects an expression of Duration type, you get stuff from the Duration companion). But I don't think that holds really well. See this: github.com/Kotlin/KEEP/issues/190#issuecomment-904805172Womble
@Caraway the function funName() is called by method() in the first example. Did you miss it or are you looking for something else?Womble
@Joffrey, right, I just want to confirm that there is no other way to use funName() outside ClassName1 scope, also, if I understand correctly there is no difference between putting funName() inside or outside the companion object because funName() can be called only in className1 scope ? thank youCaraway
@Womble we can call funName() outside ClassName1 scope only if we import ClassName1.Companion.funName and maybe this can be the only use of putting the extension inside the companion object ?Caraway
@imane whether you can call it outside ClassName1 depends on whether the visibility allows it. And of course if the visibility allows it you still need to import it. When the visibility is private, there is still a difference between declaring in the class or in the companion. When it's in the companion, the body of the function doesn't have access to class members, which might be desirable sometimes to express that the function is pureWomble
W
2

This is a weird syntax. One example to use this can be:

import ClassName1.Companion.funName

class ClassName1 {
    companion object {
        fun ClassName2.funName() = ""
    }
}

class ClassName2

fun main() {
    ClassName2().funName()
}

Here I had to import funName from Class1.Companion in order to call it. There's no easy way to call this function.

If you don't have past experience with extension functions, you can take a look at the decompiled bytecode to see what's happening under the hood:

public final class ClassName2 {
}

public final class ClassName1 {
   @NotNull
   public static final ClassName1.Companion Companion = new ClassName1.Companion((DefaultConstructorMarker)null);

   public static final class Companion {
      @NotNull
      public final String funName(@NotNull ClassName2 $this$funName) {
         return "";
      }

      private Companion() {
      }

      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

public final class YourFileNameKt {
   public static final void main() {
      ClassName1.Companion.funName(new ClassName2());
   }
}

funName is a function inside static Companion class declared inside ClassName1. It receives a ClassName2 object as a parameter (this is how extension functions work, nothing special here).

But I would say that this type of declaration is very confusing. It would be better if you could provide more info on how this is being used in your case. Passing a ClassName2 object directly in the function seems to be a much cleaner approach here.

Whirlybird answered 7/6, 2022 at 15:7 Comment(1)
They use this pattern for the Duration extension properties in the standard library (but I find it very annoying). github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/…Kepi

© 2022 - 2024 — McMap. All rights reserved.