美文网首页
Kotlin代码规范(一)

Kotlin代码规范(一)

作者: 搞Android的文艺青年 | 来源:发表于2019-08-02 15:35 被阅读0次

    1. kotlin静态常量

    众所周知,java中的静态常量定义如下:

    public static final boolean DEBUG = true;
    

    但由于java是面向对象的,所以java中的静态常量必须由类来承载,如:

    public class Constants {
        public static final boolean DEBUG = true;
    }
    

    kotlin中是没有static关键字的,那么如何定义静态常量呢?
    kotlin中采用const关键字来定义常量 如:

    public const val DEBUG = true // kotlin会自动判断变量类型
    
    那么按照java的编码方式的话,直接放到类中定义的话,会出现编译问题 编译失败.png

    kotlin给出的两种解决方案:

    1. on top level

      kotlin非常推荐的一种方式,也就是将静态常量的定义放到类的外面,不依赖类的而存在,如: top level.png 既然可以不依赖类而存在,那么可以改成这样(public 关键字可以省略): 取消类的定义.png

      这种方式定义的静态常量的使用:
      java类中使用:

    public class TestJava {
        public void test() {
            // 直接采用常量定位位置的文件名+Kt为类名调用
            if (ConstantsKt.DEBUG) {
                
            }
        }
    }
    

    kotlin中使用:

    class Test {
        fun testConstant() {
           // 直接使用变量
           if (DEBUG) {
                
            }
        }
    }
    

    接下来到了答疑的时候了,为什么java类中直接采用文件名+Kt的方式调用呢?
    首先kotlin和java之间之所以能无缝对接就是因为无论是java还是kotlin,最后都会被编译成dex字节码(android虚拟键最终执行的就是dex字节码),java经历 .java源文件-> .class java可执行文件-> dex字节码;kotlin经历 .kt源文件->dex字节码。
    那么看一下kotlin编译的字节码是什么吧,Tools -> Kotlin -> Show Kotlin Bytecode

    // ================com/xpro/camera/common/prop/ConstantsKt.class =================
    // class version 50.0 (50)
    // access flags 0x31
    public final class com/xpro/camera/common/prop/ConstantsKt {
    
    
      // access flags 0x19
      public final static Z DEBUG = true
    
      @Lkotlin/Metadata;(mv={1, 1, 15}, bv={1, 0, 3}, k=2, d1={"\u0000\u0008\n\u0000\n\u0002\u0010\u000b\n\u0000\"\u000e\u0010\u0000\u001a\u00020\u0001X\u0086T\u00a2\u0006\u0002\n\u0000\u00a8\u0006\u0002"}, d2={"DEBUG", "", "common_debug"})
      // compiled from: Constants.kt
    }
    
    
    // ================META-INF/common_debug.kotlin_module =================
    
    *
    com.xpro.camera.common.prop ConstantsKt
    
    
    这样的代码我们显然看的不是很方便,那么有没有一种办法把字节码转换成java.class 呢? 显然是有的,就是刚刚工具中的Decompile。 decompile.png

    经过转换后的java class如下:

    import kotlin.Metadata;
    
    @Metadata(
       mv = {1, 1, 15},
       bv = {1, 0, 3},
       k = 2,
       d1 = {"\u0000\b\n\u0000\n\u0002\u0010\u000b\n\u0000\"\u000e\u0010\u0000\u001a\u00020\u0001X\u0086T¢\u0006\u0002\n\u0000¨\u0006\u0002"},
       d2 = {"DEBUG", "", "common_debug"}
    )
    public final class ConstantsKt {
       public static final boolean DEBUG = true;
    }
    
    

    是不是有一种恍然大明白的感觉呢?这不就是java中的静态常量吗?我当然知道java的静态常量怎么调用啊,直接类名+.。所以上面的问题就迎刃而解了,知其然,知其所以然。

    1. in objects
      第二种 in objects, 两种写法,一种是在用object修饰的kotlin单例中,一种是采用伴生对象,如:
    /**
     * object关键字在kotlin中表示懒汉式单例,对象在kotlin中用Any表示。
     */
    object Constants {
        public const val DEBUG= true
    }
    
    /**
    * 伴生对象实现
    */
    class Constants {
        companion object {
            public const val DEBUG= true
        }
    }
    

    接下来同样说说in objects调用方式,这两种写法,在java和kotlin中调用方式是一样的,都是直接用类名+. ,这里就不贴代码了。
    那么同样要知其所以然,重复刚刚的转换操作,得到java代码,如下:

    // 第一种object关键字转换而来的
    import kotlin.Metadata;
    
    public final class Constants {
       public static final boolean DEBUG = true;
       public static final Constants INSTANCE;
    
       private Constants() {
       }
    
       static {
          Constants var0 = new Constants();
          INSTANCE = var0;
       }
    }
    
    // 这是 companion object(伴生对象)转换而来的
    import kotlin.Metadata;
    import kotlin.jvm.internal.DefaultConstructorMarker;
    
    public final class Constants {
       public static final boolean DEBUG = true;
       public static final Constants.Companion Companion = new Constants.Companion((DefaultConstructorMarker)null);
    
       public static final class Companion {
          private Companion() {
          }
    
          // $FF: synthetic method
          public Companion(DefaultConstructorMarker $constructor_marker) {
             this();
          }
       }
    }
    

    显然,结果还是java的静态常量,但比之top level的区别是生成了其他的对象,而我们想要的只是静态常量,不需要浪费更多的资源,所以最终结论就是:

    kotlin类中只定义静态常量时 使用 top level为最佳方案。

    既然 top level 为最佳方案,那么object和companion object 又有什么用呢?别急,下面继续。

    2. kotlin单例

    在码砖过程中,总要用点稍微高级一点的东西,单例这个设计模式大家都不陌生,在java中玩得6的,什么懒汉式、饿汉式,什么Double Check,线程安全等等,这不是重点,贴个我觉得写得不错的链接吧。

    https://www.cnblogs.com/zhaosq/p/10135362.html

    下面主角来了,kotlin单例:

    实现方式一: object关键字
    object SingleInstance {
        fun debugEnable(): Boolean {
            return true
        }
    }
    

    kotlin中使用:

    class Test {
        fun testInstance() {
            SingleInstance.debugEnable()        
        }
    }
    

    java中使用:

    class Test {
        public void testInstance() {
            // object关键定义的单例,需要使用INSTANCE对象
            SingleInstance.INSTANCE.debugEnable();
        }
    }
    

    对应的转换后的java代码:

    import kotlin.Metadata;
    
    public final class SingleInstance {
       public static final SingleInstance INSTANCE;
    
       public final boolean debugEnable() {
          return true;
       }
    
       private SingleInstance() {
       }
    
       static {
          SingleInstance var0 = new SingleInstance();
          INSTANCE = var0;
       }
    }
    

    恍然大明白了!!! java的饿汉式啊。-_-||

    实现方式二:by lazy 懒加载

    懒加载时,定义的单例类就是普通的class,只是使用的时候通过懒加载实现单例的功能,也算是它山之石可以攻玉了。直接看使用吧。

    class Test {
        private val instance by lazy { SingleInstance() }
        
        fun testInstance() {
            instance.debugEnable()
        }
    }
    

    by lazy关键字只是kotlin中提供,那么它是如何实现懒加载以及单例的呢,同样,看看转换后的java代码:

    import kotlin.Lazy;
    import kotlin.LazyKt;
    import kotlin.Metadata;
    import kotlin.jvm.functions.Function0;
    import kotlin.jvm.internal.PropertyReference1Impl;
    import kotlin.jvm.internal.Reflection;
    import kotlin.reflect.KProperty;
    
    public final class Test {
       // $FF: synthetic field
       static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Test.class), "instance", "getInstance()Lcom/xpro/camera/common/prop/SingleInstance;"))};
       private final Lazy instance$delegate;
    
       private final SingleInstance getInstance() {
          Lazy var1 = this.instance$delegate;
          KProperty var3 = $$delegatedProperties[0];
          return (SingleInstance)var1.getValue();
       }
    
       public final void testConstant() {
          this.getInstance().debugEnable();
       }
    
       public Test() {
          // 用到了LazyKt
          this.instance$delegate = LazyKt.lazy((Function0)null.INSTANCE);
       }
    

    by lazy 同样不展开说,想要了解的,同样贴一片文章

    http://www.imooc.com/article/details/id/280070

    接下来,如果在单例中需要传递参数呢?比如常用的Context,object关键字肯定是不行了,by lazy呢?这个当然可以,下面要说的就是

    方式三 companion object 伴生对象

    直接上代码:Double Check + 线程安全

    import android.content.Context
    
    class SingleInstance(context: Context) {
    
        companion object {
    
            @Volatile
            private var instance: SingleInstance? = null
    
            fun getInstance(context: Context): SingleInstance {
                if (instance == null) {
                    synchronized(this) {
                        if (instance == null) {
                            instance = SingleInstance(context.applicationContext)
                        }
                    }
                }
                return instance!!
            }
        }
    
        fun debugEnable(): Boolean {
            return true
        }
    }
    

    那有没有更风骚一点的写法呢?不是都是kotlin简单、代码少吗?这个单例代码一点也不少啊,别急,改造中...

    import android.content.Context
    
    class SingleInstance(context: Context) {
    
        companion object {
    
            @Volatile
            private var instance: SingleInstance? = null
    
            fun getInstance(context: Context) = instance ?: synchronized(this) {
                instance ?: SingleInstance(context.applicationContext).apply { instance = this }
            }
        }
    
        fun debugEnable(): Boolean {
            return true
        }
    }
    

    666的飞起︿( ̄︶ ̄)︿
    具体的转换后的java代码,这里就不贴了,说说调用吧

    class Test {
        // kotlin调用
        fun testInstance(context: Context) {
            val enable = SingleInstance.getInstance(context).debugEnable()
        }
    }
    
    public class TestJava {
        // java调用, 多了个Companion
        public void testInstance(Context context) {
            boolean enable = SingleInstance.Companion.getInstance(context).debugEnable();
        }
    }
    

    又有人会问了,这多不方便啊,java调用还得加Companion,能不能不加啊?
    --可以,满足一切需求。

    class SingleInstance(context: Context) {
    
        companion object {
    
            @Volatile
            private var instance: SingleInstance? = null
    
            @JvmStatic // 加上 @JvmStatic 调用的时候就可以直接和Kotlin中一样了
            fun getInstance(context: Context) = instance ?: synchronized(this) {
                instance ?: SingleInstance(context.applicationContext).apply { instance = this }
            }
        }
    
        fun debugEnable(): Boolean {
            return true
        }
    }
    
    这下就喜大普奔了,no ~,真的是这样吗?肯定不是啊。 double method.png

    聪明的你一定发现了什么!是的,这就是我下面要说的。next please !

    3. kotlin伴生对象

    所谓伴生对象就是跟随一起出生的,简单理解。和java中的静态内部类是一样的。
    先说上面的问题吧,红色方框框起来的地方有两个getInstance方法,可以我们明明只定义了一个啊,难道这个锅是伴生对象的吗?不,这个锅伴生对象不背。具体是因为@JvmStatic关键字引起的,因为我们要求在java中调用的时候直接使用类名调用,所以额外生成了一个静态方法。所以结论来了:

    companion object中定义的方法,添加@JvmStatic注解后,编译后方法数会变Double

    所以是选择在java中调用的时候多加个Companion呢?还是选择方法数Double呢?随你喜欢了。当然你可以把项目全部采用kotlin来写,这样就不需要加@JvmStatic注解了,也就不需要纠结给java调用的问题了。(这个有点难-_-||)

    那么除了这些,关于伴生对象我还要说些什么呢?

    慎用companion object

    由于伴生对象会在类的内部创建一个静态常量的对象,不利于资源的回收,如果只定义静态常量和静态方法的话不推荐使用,当然如果创建带参数的单例的话可以使用,但@JvmStatic关键字最好别用 ,还有就是如果单例数量特别多,那么Companion静态常量消耗的资源就比较大了,这个时候最好使用by lazy 或者自行定义对象池进行优化。

    说了这么多,看的也有点累了,敲黑板划重点了。

    1. 定义静态常量和静态方法,最好使用top level 方式,在java中调用时采用xxxKt的方式调用;kotlin中直接使用。
    2. 定义单例最好使用by lazy或者自定定义对象池进行优化。
    3. 慎用@JvmStatic注解(Double Method问题)
    4. 谨慎使用companion object(静态内部对象资源回收问题)

    好了,结束。。耗时好久-_-||

    相关文章

      网友评论

          本文标题:Kotlin代码规范(一)

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