1. 简介

在 Kotlin 中,companion object(伴生对象) 是类内部的一个特殊对象,用于定义静态方法和属性。然而,在实际开发中我们经常会遇到这样一个需求:想从伴生对象内部调用定义在类主体中的非静态方法或属性

这看似违反直觉——毕竟伴生对象是“静态”的,而外部方法依赖实例。但通过一些技巧,完全可以实现。本文将系统介绍几种实用的解决方案,帮助你在不同场景下优雅地解决这个问题。

⚠️ 注意:这类需求通常出现在工具类、工厂模式或配置初始化等场景。如果频繁出现,建议重新审视设计,避免过度耦合。

2. 使用对外部类的引用

最直接的方式是在伴生对象中持有一个外部类的实例引用,然后通过该实例调用普通方法。

class OuterClass {
    companion object {
        val outerClass: OuterClass = OuterClass()
        fun companionMethod(): String {
            return outerClass.outerClassMethod()
        }
    }

    fun outerClassMethod(): String {
        return "This is a method outside the companion object"
    }
}

说明

  • companion object 中直接创建了 OuterClass 的实例。
  • 通过这个实例调用了 outerClassMethod()

⚠️ 踩坑提醒

  • 这种方式会触发构造函数执行,若构造函数有副作用(如资源加载、网络请求),可能引发意料之外的行为。
  • 每次加载类时都会创建一个实例,即使从未使用过,存在潜在内存浪费。

测试验证:

@Test
fun `calls outer method using reference to outer class`() {
    val result = OuterClass.companionMethod()
        
    assertEquals("This is a method outside the companion object", result)
}

3. 将外部类实例作为参数传入

更灵活的设计是:不主动持有实例,而是由调用方传入。为了增强解耦,推荐结合接口使用。

interface OuterClassInterface {
    fun outerClassMethod(): String
}

class OuterClassWithInterface : OuterClassInterface {
    companion object {
        fun companionMethod(outerClassInterface: OuterClassInterface): String {
            return outerClassInterface.outerClassMethod()
        }
    }

    override fun outerClassMethod(): String {
        return "This is a method outside the companion object"
    }
}

优势

  • 解耦清晰,符合依赖倒置原则。
  • 可用于单元测试中注入 Mock 实例。
  • 避免提前初始化带来的性能开销。

测试代码如下:

@Test
fun `calls outer method using interface`() {
    val myClass = OuterClassWithInterface()
    val result = OuterClassWithInterface.companionMethod(myClass)

    assertEquals("This is a method outside the companion object", result)
}

💡 适用场景
当你需要在静态上下文中处理多个不同类型但具有相同行为的对象时,这种模式特别有用,比如事件处理器、策略模式等。

4. 使用 Object 声明或单例类

Kotlin 的 object 关键字天然支持单例模式,无需手动实现。我们可以把要调用的方法放在一个独立的 object 中,然后在伴生对象里直接访问。

class ClassWithObject {
    companion object {
        fun companionMethod(): String {
            return ObjectClass.outerClassMethod()
        }
    }

    object ObjectClass {
        fun outerClassMethod(): String {
            return "This is a method outside the companion object"
        }
    }
}

特点

  • ObjectClass 是懒加载的单例,第一次访问时初始化。
  • 不依赖外部类实例,线程安全。
  • 调用简洁,适合工具方法集中管理。

测试验证:

@Test
fun `calls outer method using object declaration`() {
    val result = ClassWithObject.companionMethod()

    assertEquals("This is a method outside the companion object", result)
}

📌 建议
对于纯功能性、无状态的方法(如格式化、计算、日志封装),优先考虑提取到 object 中统一维护。

5. 使用函数引用(Function Reference)

这是最灵活也最具 Kotlin 风格的做法——利用高阶函数和函数引用机制,将方法当作参数传递。

class ClassWithFunctionReference {
    companion object {
        fun companionMethod(outerClassMethod: () -> String): String {
            return outerClassMethod()
        }
    }

    fun outerClassMethod(): String {
        return "This is a method outside the companion object"
    }
}

核心机制

  • companionMethod 接收一个类型为 () -> String 的函数作为参数。
  • 外部调用时通过 instance::methodName 获取函数引用。

测试示例:

@Test
fun `calls outer method using function reference`() {
    val myClass = ClassWithFunctionReference()
    val result = ClassWithFunctionReference.companionMethod(myClass::outerClassMethod)

    assertEquals("This is a method outside the companion object", result)
}

优点

  • 极高的可复用性:同一个 companionMethod 可以接受任意满足签名的函数。
  • 支持 Lambda 表达式,扩展性强。
  • 易于做 AOP、装饰器模式等高级操作。

缺点

  • 调用方必须显式传参,不够“透明”。
  • 对阅读代码的人有一定理解门槛。

💡 典型用途
适合作为通用处理器框架的基础,例如统一异常处理包装、耗时统计、缓存代理等。

6. 总结

方式 适用场景 是否推荐
直接实例化外部类 快速原型、简单工具类 ⚠️ 谨慎使用
参数传入 + 接口 多态逻辑、测试友好 ✅ 推荐
使用 object 单例 工具方法、无状态功能 ✅ 强烈推荐
函数引用 高阶抽象、通用处理器 ✅ 推荐

选择哪种方案,取决于你的具体需求:

  • 如果方法本身是通用的、无状态的 ➜ 提取到 object
  • 如果需要根据不同实现动态调用 ➜ 使用接口参数
  • 如果追求极致灵活性和可组合性 ➜ 使用函数引用
  • 简单项目快速上手 ➜ 可临时使用直接实例化(但记得后期重构)

📌 最后提醒:不要为了用伴生对象而强行设计。当发现频繁需要从 companion object 访问实例方法时,可能是设计上出现了“静态与实例职责混淆”,不妨重新思考类的职责划分是否合理。


原始标题:Accessing Methods Outside Companion Object in Kotlin