美文网首页Kotlin
Kotlin:由object和companion object创

Kotlin:由object和companion object创

作者: 512DIDIDI | 来源:发表于2019-01-11 17:39 被阅读0次

    kotlin中使用了 objectcompanion object 关键字用来表示java中的静态成员(类似静态成员)。
    在实现双重校验锁单例模式时,我尝试了objectcompanion object,在网上想查询这两者的单例有什么区别,但好像也没查到什么资料。
    先贴单例代码:

    /**
     * kotlin双重校验锁单例模式
     */
    object Singleton {
    
      @Volatile
      private var instance: ObjectExpression? = null
    
      fun getInstance() = instance ?: synchronized(this) {
        instance ?: ObjectExpression().apply {
            instance = this
        }
      }
    }
    
    class ObjectExpression {
    
      companion object {
        @Volatile
        private var instance: ObjectExpression? = null
    
        fun getInstance() = instance ?: synchronized(this) {
            instance ?: ObjectExpression().apply {
                instance = this
            }
        }
      }
    }
    
    • 前者是在object中声明ObjectExpression的单例
    • 后者是在ObjectExpressioncompanion object声明的单例

    反编译为java看看两者区别:

    public final class Singleton {
      private static volatile ObjectExpression instance;
      public static final Singleton INSTANCE;
    
      @NotNull
      public final ObjectExpression getInstance() {
        ObjectExpression var10000 = instance;
        if (instance == null) {
         synchronized(this){}
    
         ObjectExpression var3;
         try {
            var10000 = instance;
            if (instance == null) {
               ObjectExpression var2 = new ObjectExpression();
               instance = var2;
               var10000 = var2;
            }
    
            var3 = var10000;
         } finally {
            ;
         }
    
         var10000 = var3;
        }
    
        return var10000;
      }
      //通过静态代码块生成INSTANCE实例
      static {
        Singleton var0 = new Singleton();
        INSTANCE = var0;
       }
    }
    
    public final class ObjectExpression {
      private static volatile ObjectExpression instance;
      //相应类加载时生成Companion对象
      public static final ObjectExpression.Companion Companion = new ObjectExpression.Companion((DefaultConstructorMarker)null);
    
      public static final class Companion {
        @NotNull
        public final ObjectExpression getInstance() {
         ObjectExpression var10000 = ObjectExpression.instance;
         if (var10000 == null) {
            synchronized(this){}
    
            ObjectExpression var3;
            try {
               var10000 = ObjectExpression.instance;
               if (var10000 == null) {
                  ObjectExpression var2 = new ObjectExpression();
                  ObjectExpression.instance = var2;
                  var10000 = var2;
               }
    
               var3 = var10000;
            } finally {
               ;
            }
    
            var10000 = var3;
          }
    
          return var10000;
        }
    
      private Companion() {
      }
    
      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
     }
    }
    

    实际调用的时候:

    Singleton.getInstance()
    ObjectExpression.getInstance()
    

    反编译为java看看两者的区别:

      Singleton.INSTANCE.getInstance();
      ObjectExpression.Companion.getInstance();
    
    • 前者调用的是Singleton当中的INSTANCE
    • 后者调用的是ObjectExpression当中给的Companion

    所以object内部是使用静态代码块来进行INSTANCE的初始化,而companion object内部是使用静态变量来进行Companion的初始化。
    不过kotlin官方文档中有这样一段话描述objectcompanion object

    对象表达式和对象声明之间的语义差异
    对象表达式和对象声明之间有一个重要的语义差别:

    • 对象表达式是在使用他们的地方立即执行(及初始化)的;
    • 对象声明是在第一次被访问到时延迟初始化的;
    • 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。

    经过一晚上的研究,终于明白了官方文档的解释,同时也发现自己基础知识的欠缺,路漫漫呀...
    那么就来说一说官方文档所说的第二句话:

    • 对象声明是在第一次被访问到时延迟初始化的;

    其实指的意思是由于object是一个独立的类(可以通过反编译java查看),因此object当中的方法第一次访问时,此时object类加载,静态代码块初始化,INSTANCE完成创建。

    而第三句话:

    • 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配。

    指的意思是companion object的外部类加载时,由于companion是静态变量,外部类加载的时候,会进行初始化,所以等同于外部类的静态成员。

    而自己知识的欠缺体现在关于类加载的时机与过程上,贴几个问题,思考一下输出结果是什么:

    public class Singleton {
    
      private static Singleton instance = new Singleton();
      public static int count1;
      public static int count2 = 0;
      private Singleton(){
        count1 ++;
        count2 ++;
      }
    
      public static Singleton getInstance(){
        return instance;
      }
    }
    
    public class Test {
      public static void main(String[] args){
        Singleton singleton = Singleton.getInstance();
        System.out.println("count1 = " + Singleton.count1);
        System.out.println("count2 = " + Singleton.count2);
      }
    }
    

    count1 = 1
    count2 = 1

    错×

    正确答案是:

    count1 = 1
    count2 = 0

    其实问题就是牵涉到类的加载与过程,虚拟机定义了以下六种情况,如果类未被初始化,则会进行初始化:

    1. 创建类的实例
    2. 访问类的静态变量(除常量【被final修辞的静态变量】原因:常量一种特殊的变量,因为编译器把他们当作值(value)而不是域(field)来对待。如果你的代码中用到了常变量(constant variable),编译器并不会生成字节码来从对象中载入域的值,而是直接把这个值插入到字节码中。这是一种很有用的优化,但是如果你需要改变final域的值那么每一块用到那个域的代码都需要重新编译。
    3. 访问类的静态方法
    4. 反射如(Class.forName("my.xyz.Test"))
    5. 当初始化一个类时,发现其父类还未初始化,则先出发父类的初始化
    6. 虚拟机启动时,定义了main()方法的那个类先初始化

    那么我们来分析以下上述代码的执行情况:

    1. main()方法 Test类初始化
    2. main()方法第一句:访问SingletongetInstance()静态方法 Singleton类初始化,此时按照代码执行顺序进行静态成员的初始化默认值
      • instance = null
      • count1 = 0
      • count2 = 0
    3. 按照代码执行顺序为类的静态成员赋值
      • private static Singleton instance = new Singleton(); instance调用Singleton的构造方法,调用构造方法后 count1 = 1,count2 = 1
      • public static int count1; count1没有进行赋值操作,所以count1 = 1
      • public static int count2 = 0; count2进行赋值操作,所以count2 = 0
    4. main()方法第二句:访问Singletoncount1变量,由于count1没有赋初始值,所以count1 = 1
    5. main()方法第三局:访问Singletoncount2变量,由于count2赋了初始值 0,所以count2 = 0

    所以如果我们把Singleton代码执行顺序变化一下:

    public class Singleton {
    
      public static int count1;
      public static int count2 = 0;
      private static Singleton instance = new Singleton();
    
      private Singleton() {
        count1++;
        count2++;
      }
    
      public static Singleton getInstance() {
        return instance;
      }
    
    }
    

    那么此时输出结果就为:

    count1 = 1
    count2 = 1

    如果改为如下代码,那么运行情况又是怎样:

    public class Singleton {
    
      Singleton(){
        System.out.println("Singleton construct");
      }
    
      static {
        System.out.println("Singleton static block");
      }
    
      public static final int COUNT = 1;
    
    }
    
    public class Test {
      public static void main(String[] args) {
        System.out.println("count = " + Singleton.COUNT);
      }
    }
    

    运行结果为:

    count = 1

    由于常量在编译阶段会存入相应类的常量池当中,所以在实际调用中Singleton.COUNT并没有直接引用到Singleton类,因此不会进行Singleton类的初始化,所以输出结果为 count = 1

    相关文章

      网友评论

        本文标题:Kotlin:由object和companion object创

        本文链接:https://www.haomeiwen.com/subject/upnxdqtx.html