Kotlin内部实现-01-companion_object
Kotlin内部实现_01_companion object
1. companion object 概述
在 Kotlin 中,companion object
是一种特殊的对象声明,它用于在类内部创建静态成员。这是 Kotlin 对 Java 中静态成员的一种替代方案,因为 Kotlin 自身不直接支持传统意义上的静态方法或属性。
主要用途和特点包括:
-
静态方法和属性:
companion object
允许你在不创建类的实例的情况下,通过类名直接访问这些方法和属性。 -
实现接口:与普通对象一样,
companion object
可以实现接口。 -
工厂方法和单例:常用于创建工厂方法或实现单例模式。
-
Java 兼容性:对于 Java 代码来说,
companion object
中的成员看起来就像是传统的静态方法和属性。
为什么需要 companion object
:
- 语言设计哲学:Kotlin 设计上倾向于更明确和安全的编程范式。通过将静态成员放在
companion object
中,Kotlin 确保了静态部分和实例部分的明确区分。 - 功能丰富:与 Java 的静态成员相比,
companion object
提供了更多的灵活性和功能,如实现接口、继承等。 - 互操作性:这种设计同时保证了与 Java 的良好互操作性,因为 Java 代码可以像访问静态成员一样访问
companion object
的成员。
总之,companion object
是 Kotlin 对 Java 静态成员概念的一种扩展和增强,使代码更加灵活和表达力更强。
2. companion object 底层实现
companion object
在底层是通过静态内部类实现的,但它提供了比传统的静态内部类更多的功能和灵活性。 在 Kotlin 中,companion object
的底层实现确实与 Java 中的静态内部类相似,但有一些独特之处。
-
静态内部类的类似性:在 JVM 字节码层面,
companion object
被编译成其外部类的一个静态内部类。这意味着,尽管在 Kotlin 语言层面你看不到传统的静态方法和属性,但在编译后的 Java 字节码中,它们是作为静态内部类实现的。 -
实例化机制:每个包含
companion object
的类都会自动拥有一个名为Companion
的静态内部类的实例。这个实例在类加载时被创建,并且是单例的。当你在 Kotlin 代码中访问companion object
的成员时,实际上是在访问这个单例实例的成员。 -
成员访问:当从 Kotlin 访问
companion object
的成员时,可以直接通过外部类的名称进行访问,这与访问 Java 中的静态成员类似。但从 Java 代码中访问时,需要通过Companion
实例来访问这些成员,除非使用@JvmStatic
注解。 -
@JvmStatic
注解:如果你想让companion object
中的某个成员真正成为 JVM 静态成员,可以使用@JvmStatic
注解。这样,从 Java 代码中访问这些成员时就不需要通过Companion
实例。 -
接口实现和扩展性:
companion object
可以实现接口和拥有自己的扩展函数,这些都是传统的静态内部类无法做到的。
3. companion object 深入验证
- 在Android Studio中编写一个测试类:
MainActivity
package com.yongdaimi.kt.kotlintest
import android.os.Bundle
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import com.yongdaimi.kx.kotlintest.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var mViewBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mViewBinding = ActivityMainBinding.inflate(LayoutInflater.from(this))
setContentView(mViewBinding.root)
}
companion object {
const val WRITE_FILE_NAME: String = "myData"
fun getNameValue() = "20"
}
}
-
依次点击菜单栏“Tools”-"Kotlin"-"Show Kotlin Bytecode":
这个时候AS会自动生成MainActivity
所对应的字节码文件:
-
反编译:
package com.yongdaimi.kx.kotlintest; import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import androidx.appcompat.app.AppCompatActivity; import com.yongdaimi.kx.kotlintest.databinding.ActivityMainBinding; import kotlin.Metadata; import kotlin.jvm.internal.DefaultConstructorMarker; import kotlin.jvm.internal.Intrinsics; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @Metadata( mv = {1, 9, 0}, k = 1, d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\u0018\u0000 \t2\u00020\u0001:\u0001\tB\u0005¢\u0006\u0002\u0010\u0002J\u0012\u0010\u0005\u001a\u00020\u00062\b\u0010\u0007\u001a\u0004\u0018\u00010\bH\u0014R\u000e\u0010\u0003\u001a\u00020\u0004X\u0082.¢\u0006\u0002\n\u0000¨\u0006\n"}, d2 = {"Lcom/yongdaimi/kx/kotlintest/MainActivity;", "Landroidx/appcompat/app/AppCompatActivity;", "()V", "mViewBinding", "Lcom/yongdaimi/kx/kotlintest/databinding/ActivityMainBinding;", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "Companion", "app_debug"} ) public final class MainActivity extends AppCompatActivity { private ActivityMainBinding mViewBinding; @NotNull public static final String WRITE_FILE_NAME = "myData"; @NotNull public static final Companion Companion = new Companion((DefaultConstructorMarker)null); protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding var10001 = ActivityMainBinding.inflate(LayoutInflater.from((Context)this)); Intrinsics.checkNotNullExpressionValue(var10001, "ActivityMainBinding.infl…ayoutInflater.from(this))"); this.mViewBinding = var10001; var10001 = this.mViewBinding; if (var10001 == null) { Intrinsics.throwUninitializedPropertyAccessException("mViewBinding"); } this.setContentView((View)var10001.getRoot()); } @Metadata( mv = {1, 9, 0}, k = 1, d1 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0002\b\u0002\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0005\u001a\u00020\u0004R\u000e\u0010\u0003\u001a\u00020\u0004X\u0086T¢\u0006\u0002\n\u0000¨\u0006\u0006"}, d2 = {"Lcom/yongdaimi/kx/kotlintest/MainActivity$Companion;", "", "()V", "WRITE_FILE_NAME", "", "getNameValue", "app_debug"} ) public static final class Companion { @NotNull public final String getNameValue() { return "20"; } private Companion() { } // $FF: synthetic method public Companion(DefaultConstructorMarker $constructor_marker) { this(); } } }
-
注意这几处:
@NotNull public static final String WRITE_FILE_NAME = "myData"; @NotNull public static final Companion Companion = new Companion((DefaultConstructorMarker)null); public static final class Companion { @NotNull public final String getNameValue() { return "20"; } private Companion() { } // $FF: synthetic method public Companion(DefaultConstructorMarker $constructor_marker) { this(); } }
-
明白了它的原理之后,就知道在java层该如何访问了:
public class SplashActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_splash); String writeFileName = MainActivity.WRITE_FILE_NAME; String nameValue = MainActivity.Companion.getNameValue(); } }
<完>