Kotlin代码规范(一)

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(静态内部对象资源回收问题)

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容