Kotlin内部实现-01-companion_object

发布时间 2023-12-22 16:42:11作者: 夜行过客

Kotlin内部实现_01_companion object

1. companion object 概述

在 Kotlin 中,companion object 是一种特殊的对象声明,它用于在类内部创建静态成员。这是 Kotlin 对 Java 中静态成员的一种替代方案,因为 Kotlin 自身不直接支持传统意义上的静态方法或属性。

主要用途和特点包括:

  1. 静态方法和属性companion object 允许你在不创建类的实例的情况下,通过类名直接访问这些方法和属性。

  2. 实现接口:与普通对象一样,companion object 可以实现接口。

  3. 工厂方法和单例:常用于创建工厂方法或实现单例模式。

  4. 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 中的静态内部类相似,但有一些独特之处。

  1. 静态内部类的类似性:在 JVM 字节码层面,companion object 被编译成其外部类的一个静态内部类。这意味着,尽管在 Kotlin 语言层面你看不到传统的静态方法和属性,但在编译后的 Java 字节码中,它们是作为静态内部类实现的。

  2. 实例化机制:每个包含 companion object 的类都会自动拥有一个名为 Companion 的静态内部类的实例。这个实例在类加载时被创建,并且是单例的。当你在 Kotlin 代码中访问 companion object 的成员时,实际上是在访问这个单例实例的成员。

  3. 成员访问:当从 Kotlin 访问 companion object 的成员时,可以直接通过外部类的名称进行访问,这与访问 Java 中的静态成员类似。但从 Java 代码中访问时,需要通过 Companion 实例来访问这些成员,除非使用 @JvmStatic 注解。

  4. @JvmStatic 注解:如果你想让 companion object 中的某个成员真正成为 JVM 静态成员,可以使用 @JvmStatic 注解。这样,从 Java 代码中访问这些成员时就不需要通过 Companion 实例。

  5. 接口实现和扩展性companion object 可以实现接口和拥有自己的扩展函数,这些都是传统的静态内部类无法做到的。

3. companion object 深入验证

  1. 在Android Studio中编写一个测试类:MainActivity
package com.realsil.rtk.kotlintest

import android.os.Bundle
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import com.realsil.rtk.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"
    }

}
  1. 依次点击菜单栏“Tools”-"Kotlin"-"Show Kotlin Bytecode":

    image-20231222163351513

这个时候AS会自动生成MainActivity所对应的字节码文件:

image-20231222163555192

  1. 反编译:

    package com.realsil.rtk.kotlintest;
    
    import android.content.Context;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    import androidx.appcompat.app.AppCompatActivity;
    import com.realsil.rtk.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/realsil/rtk/kotlintest/MainActivity;", "Landroidx/appcompat/app/AppCompatActivity;", "()V", "mViewBinding", "Lcom/realsil/rtk/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/realsil/rtk/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();
          }
       }
    }
    
    
  2. 注意这几处:

       @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();
          }
       }
    
  3. 明白了它的原理之后,就知道在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();
        }
    
    }
    

<完>