Kotlin:伴生对象内的函数扩展

5
在Kotlin语言中,这个语法是做什么的,它是如何工作的?
class ClassName1 {
    companion object {          
        fun ClassName2.funName()=""         
    } 
}

我冒昧地编辑了代码的风格,使其符合 Kotlin 的约定并且可以编译。如果这不是您想要的,请随时更正。 - Joffrey
如果这是来自实际例子,你可以分享一下这个扩展函数的使用方式,这样我们就能解释为什么要以这种方式声明它。这也将使我们能够提到为什么不能用其他方式完成,或者是否实际上可以用其他方式完成。 - Joffrey
2个回答

7
这里有多个因素需要考虑:
  • 扩展函数:它们允许像使用接收者类型(即函数名称左侧的类型)的函数一样使用函数。
  • 作为成员使用的扩展函数:仅在封闭类(分发接收器)处于范围内时,可以在接收者(称为扩展接收器)上使用的扩展函数。
  • 伴生对象:与类相关联的特殊object,可以通过仅使用类名来引用。
在伴生对象内声明成员扩展函数可能对不同的事情有用。
例如,此类函数可以在类内部使用,就好像扩展函数是在类成员之外但在伴生对象中声明一样。有些人喜欢将它们放在伴生对象中,以明确它们不依赖于该类的状态(像 Java 的静态函数)。
class ClassName1 {

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

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

然而,这种用法并不要求函数必须是公开的。

另一种使用方式是将伴生对象作为一种范围的方式:

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

或者,如果你从伴侣中导入该功能,则可以直接使用:

import ClassName1.Companion.funName

val something = ClassName2()
something.funName()

这种模式例如用于Duration.Compaion,用于定义数字类型的扩展(这些是扩展属性,但其实是相同的思想): https://github.com/JetBrains/kotlin/blob/6a670dc5f38fc73eb01d754d8f7c158ae0176ceb/libraries/stdlib/src/kotlin/time/Duration.kt#L71

@Joffrey,你知道为什么他们选择将那些持续时间扩展属性放在Duration.Companion中吗?我觉得这非常不方便,因为在使用这些属性之前,你必须手动输入导入语句,并记住它们的位置。他们在同一文件中弃用了裸属性。也许是为了避免混乱的命名空间?但是数字本身并没有很多成员。 - Tenfour04
1
@Tenfour04 我也对这个选择感到失望。我认为原因是为了让他们以后通过改进 IDEA 的上下文感知完成功能来限制这些常见类型中不需要的条目(如果您在期望 Duration 类型表达式的上下文中自动完成,则会从 Duration companion 中获取内容)。但我不认为这个理由很好。请参见此链接:https://github.com/Kotlin/KEEP/issues/190#issuecomment-904805172 - Joffrey
@Imane,第一个例子中funName()函数是由method()调用的。你是错过了它还是在寻找其他东西? - Joffrey
@ Joffrey,没错,我只想确认一下,在 ClassName1 范围外没有其他使用 funName() 的方法,而且如果我理解正确,将 funName() 放在伴生对象内或外没有区别,因为只能在 className1 范围内调用 funName()?谢谢。 - Imane
1
@imane,你是否可以在ClassName1之外调用它取决于其可见性。当然,如果可见性允许,你仍然需要导入它。当可见性为“private”时,在类中声明和在伴生对象中声明之间仍然存在差异。当在伴生对象中声明时,函数体无法访问类成员,这有时可能是期望的,以表达该函数是纯函数。 - Joffrey
显示剩余2条评论

2

这是一种奇怪的语法。使用此语法的一个例子可以是:

import ClassName1.Companion.funName

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

class ClassName2

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

这里我需要从Class1.Companion导入funName才能调用它。没有简单的方法可以调用此函数。

如果您没有使用扩展函数的经验,可以查看反编译的字节码以了解底层发生了什么:

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是在ClassName1中声明的静态Companion类内部的一个函数。它接收一个ClassName2对象作为参数(这就是扩展函数的工作原理,没有什么特别之处)。
但我认为这种声明方式非常令人困惑。如果您能提供有关如何在您的情况下使用此函数的更多信息,那将更好。直接在函数中传递一个ClassName2对象似乎是一种更清晰的方法。

他们在标准库中的Duration扩展属性中使用了这种模式(但我觉得它非常烦人)。https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/time/Duration.kt - Tenfour04

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接