Kotlin代碼規(guī)范(一)

1. kotlin靜態(tài)常量

眾所周知,java中的靜態(tài)常量定義如下:

public static final boolean DEBUG = true;

但由于java是面向?qū)ο蟮?,所以java中的靜態(tài)常量必須由類來承載,如:

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

kotlin中是沒有static關(guān)鍵字的,那么如何定義靜態(tài)常量呢?
kotlin中采用const關(guān)鍵字來定義常量 如:

public const val DEBUG = true // kotlin會(huì)自動(dòng)判斷變量類型

那么按照java的編碼方式的話,直接放到類中定義的話,會(huì)出現(xiàn)編譯問題
編譯失敗.png

kotlin給出的兩種解決方案:

  1. on top level

    kotlin非常推薦的一種方式,也就是將靜態(tài)常量的定義放到類的外面,不依賴類的而存在,如:
    top level.png
    既然可以不依賴類而存在,那么可以改成這樣(public 關(guān)鍵字可以省略):
    取消類的定義.png

    這種方式定義的靜態(tài)常量的使用:
    java類中使用:

public class TestJava {
    public void test() {
        // 直接采用常量定位位置的文件名+Kt為類名調(diào)用
        if (ConstantsKt.DEBUG) {
            
        }
    }
}

kotlin中使用:

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

接下來到了答疑的時(shí)候了,為什么java類中直接采用文件名+Kt的方式調(diào)用呢?
首先kotlin和java之間之所以能無縫對(duì)接就是因?yàn)闊o論是java還是kotlin,最后都會(huì)被編譯成dex字節(jié)碼(android虛擬鍵最終執(zhí)行的就是dex字節(jié)碼),java經(jīng)歷 .java源文件-> .class java可執(zhí)行文件-> dex字節(jié)碼;kotlin經(jīng)歷 .kt源文件->dex字節(jié)碼。
那么看一下kotlin編譯的字節(jié)碼是什么吧,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

這樣的代碼我們顯然看的不是很方便,那么有沒有一種辦法把字節(jié)碼轉(zhuǎn)換成java.class 呢? 顯然是有的,就是剛剛工具中的Decompile。
decompile.png

經(jīng)過轉(zhuǎn)換后的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中的靜態(tài)常量嗎?我當(dāng)然知道java的靜態(tài)常量怎么調(diào)用啊,直接類名+.。所以上面的問題就迎刃而解了,知其然,知其所以然。

  1. in objects
    第二種 in objects, 兩種寫法,一種是在用object修飾的kotlin單例中,一種是采用伴生對(duì)象,如:
/**
 * object關(guān)鍵字在kotlin中表示懶漢式單例,對(duì)象在kotlin中用Any表示。
 */
object Constants {
    public const val DEBUG= true
}
/**
* 伴生對(duì)象實(shí)現(xiàn)
*/
class Constants {
    companion object {
        public const val DEBUG= true
    }
}

接下來同樣說說in objects調(diào)用方式,這兩種寫法,在java和kotlin中調(diào)用方式是一樣的,都是直接用類名+. ,這里就不貼代碼了。
那么同樣要知其所以然,重復(fù)剛剛的轉(zhuǎn)換操作,得到j(luò)ava代碼,如下:

// 第一種object關(guān)鍵字轉(zhuǎn)換而來的
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(伴生對(duì)象)轉(zhuǎn)換而來的
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();
      }
   }
}

顯然,結(jié)果還是java的靜態(tài)常量,但比之top level的區(qū)別是生成了其他的對(duì)象,而我們想要的只是靜態(tài)常量,不需要浪費(fèi)更多的資源,所以最終結(jié)論就是:

kotlin類中只定義靜態(tài)常量時(shí) 使用 top level為最佳方案。

既然 top level 為最佳方案,那么object和companion object 又有什么用呢?別急,下面繼續(xù)。

2. kotlin單例

在碼磚過程中,總要用點(diǎn)稍微高級(jí)一點(diǎn)的東西,單例這個(gè)設(shè)計(jì)模式大家都不陌生,在java中玩得6的,什么懶漢式、餓漢式,什么Double Check,線程安全等等,這不是重點(diǎn),貼個(gè)我覺得寫得不錯(cuò)的鏈接吧。

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

下面主角來了,kotlin單例:

實(shí)現(xiàn)方式一: object關(guān)鍵字
object SingleInstance {
    fun debugEnable(): Boolean {
        return true
    }
}

kotlin中使用:

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

java中使用:

class Test {
    public void testInstance() {
        // object關(guān)鍵定義的單例,需要使用INSTANCE對(duì)象
        SingleInstance.INSTANCE.debugEnable();
    }
}

對(duì)應(yīng)的轉(zhuǎn)換后的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的餓漢式啊。-_-||

實(shí)現(xiàn)方式二:by lazy 懶加載

懶加載時(shí),定義的單例類就是普通的class,只是使用的時(shí)候通過懶加載實(shí)現(xiàn)單例的功能,也算是它山之石可以攻玉了。直接看使用吧。

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

by lazy關(guān)鍵字只是kotlin中提供,那么它是如何實(shí)現(xiàn)懶加載以及單例的呢,同樣,看看轉(zhuǎn)換后的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

接下來,如果在單例中需要傳遞參數(shù)呢?比如常用的Context,object關(guān)鍵字肯定是不行了,by lazy呢?這個(gè)當(dāng)然可以,下面要說的就是

方式三 companion object 伴生對(duì)象

直接上代碼: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
    }
}

那有沒有更風(fēng)騷一點(diǎn)的寫法呢?不是都是kotlin簡單、代碼少嗎?這個(gè)單例代碼一點(diǎn)也不少啊,別急,改造中...

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的飛起︿( ̄︶ ̄)︿
具體的轉(zhuǎn)換后的java代碼,這里就不貼了,說說調(diào)用吧

class Test {
    // kotlin調(diào)用
    fun testInstance(context: Context) {
        val enable = SingleInstance.getInstance(context).debugEnable()
    }
}
public class TestJava {
    // java調(diào)用, 多了個(gè)Companion
    public void testInstance(Context context) {
        boolean enable = SingleInstance.Companion.getInstance(context).debugEnable();
    }
}

又有人會(huì)問了,這多不方便啊,java調(diào)用還得加Companion,能不能不加?。?br> --可以,滿足一切需求。

class SingleInstance(context: Context) {

    companion object {

        @Volatile
        private var instance: SingleInstance? = null

        @JvmStatic // 加上 @JvmStatic 調(diào)用的時(shí)候就可以直接和Kotlin中一樣了
        fun getInstance(context: Context) = instance ?: synchronized(this) {
            instance ?: SingleInstance(context.applicationContext).apply { instance = this }
        }
    }

    fun debugEnable(): Boolean {
        return true
    }
}

這下就喜大普奔了,no ~,真的是這樣嗎?肯定不是啊。
double method.png

聰明的你一定發(fā)現(xiàn)了什么!是的,這就是我下面要說的。next please !

3. kotlin伴生對(duì)象

所謂伴生對(duì)象就是跟隨一起出生的,簡單理解。和java中的靜態(tài)內(nèi)部類是一樣的。
先說上面的問題吧,紅色方框框起來的地方有兩個(gè)getInstance方法,可以我們明明只定義了一個(gè)啊,難道這個(gè)鍋是伴生對(duì)象的嗎?不,這個(gè)鍋伴生對(duì)象不背。具體是因?yàn)锧JvmStatic關(guān)鍵字引起的,因?yàn)槲覀円笤趈ava中調(diào)用的時(shí)候直接使用類名調(diào)用,所以額外生成了一個(gè)靜態(tài)方法。所以結(jié)論來了:

companion object中定義的方法,添加@JvmStatic注解后,編譯后方法數(shù)會(huì)變Double

所以是選擇在java中調(diào)用的時(shí)候多加個(gè)Companion呢?還是選擇方法數(shù)Double呢?隨你喜歡了。當(dāng)然你可以把項(xiàng)目全部采用kotlin來寫,這樣就不需要加@JvmStatic注解了,也就不需要糾結(jié)給java調(diào)用的問題了。(這個(gè)有點(diǎn)難-_-||)

那么除了這些,關(guān)于伴生對(duì)象我還要說些什么呢?

慎用companion object

由于伴生對(duì)象會(huì)在類的內(nèi)部創(chuàng)建一個(gè)靜態(tài)常量的對(duì)象,不利于資源的回收,如果只定義靜態(tài)常量和靜態(tài)方法的話不推薦使用,當(dāng)然如果創(chuàng)建帶參數(shù)的單例的話可以使用,但@JvmStatic關(guān)鍵字最好別用 ,還有就是如果單例數(shù)量特別多,那么Companion靜態(tài)常量消耗的資源就比較大了,這個(gè)時(shí)候最好使用by lazy 或者自行定義對(duì)象池進(jìn)行優(yōu)化。

說了這么多,看的也有點(diǎn)累了,敲黑板劃重點(diǎn)了。

  1. 定義靜態(tài)常量和靜態(tài)方法,最好使用top level 方式,在java中調(diào)用時(shí)采用xxxKt的方式調(diào)用;kotlin中直接使用。
  2. 定義單例最好使用by lazy或者自定定義對(duì)象池進(jìn)行優(yōu)化。
  3. 慎用@JvmStatic注解(Double Method問題)
  4. 謹(jǐn)慎使用companion object(靜態(tài)內(nèi)部對(duì)象資源回收問題)

好了,結(jié)束。。耗時(shí)好久-_-||

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容