为什么Kotlin没有static,却有companion object?深入解析性能与逻辑
如果您从Java转向Kotlin,那么您在StackOverflow上遇到的第一个问题(这个问题确实获得了数千次浏览)会是:“为什么我们要用companion object代替Java static?这会不会拖慢应用程序?”
老实说:当我第一次看到companion object这个结构时,我觉得Kotlin团队只是想把事情搞复杂。为什么要写更多的代码,而不是直接复制Java中的static?但深入研究后,我明白了:这不是一时兴起,而是一个深思熟虑的架构决策,它让代码更清晰、更安全……而且,是的,有时甚至更快。
在本文中,我们不仅仅是翻译StackOverflow上的答案。我们将深入解析companion object在底层是如何工作的,将其性能与Java static进行比较,并展示何时使用它,以及何时选择其他方式。
用简单的话说,什么是companion object?
在Java中,静态字段和方法属于类,而不是实例。Kotlin决定放弃static关键字,因为它违反了面向对象编程原则(静态不能被重写,它不是对象的一部分)。相反,Kotlin提供了伴生对象。
假设您有一个User类。您想存储一个常量MAX_AGE和一个工厂方法createGuest()。在Java中,您会这样写:
public class User { public static final int MAX_AGE = 120; public static User createGuest() { return new User("guest", "1234"); }}在Kotlin中,它看起来像这样:
class User(private val name: String, private val password: String) { companion object { const val MAX_AGE = 120 fun createGuest() = User("guest", "1234") }}请注意:companion object不仅仅是“static块”。它是一个完整的对象,存在于您的类内部。它可以有接口、扩展,甚至有自己的名称(如果您指定的话)。
Companion object在底层是如何工作的?(从JVM视角看)
为了理解性能,让我们看看字节码。当您编写companion object时,Kotlin编译器会执行以下操作:
- 创建一个名为
Companion(或您自定义的名称)的内部静态类。 - 将companion object中的所有字段和方法放入这个类中。
- 如果您使用
@JvmStatic注解,编译器还会在原始类中额外生成静态包装方法。
使用@JvmStatic注解的示例:
class User { companion object { @JvmStatic fun createGuest() = User() }}现在,从Java中您可以像调用普通静态方法一样调用User.createGuest()。如果没有这个注解,您必须写成User.Companion.createGuest()。
性能结论:如果您只使用Kotlin,则没有额外的开销。如果从Java调用,则多了一层间接性(通过Companion对象)。但JVM的JIT编译器能很好地内联这些调用,因此速度差异趋近于零。
性能:companion object vs Java static
让我们破除一个主要迷思:companion object不会拖慢速度。我进行了一些微基准测试(使用Kotlin 1.9,JVM 17),结果如下:
| 场景 | Java static |
|---|