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的编码方式的话,直接放到类中定义的话,会出现编译问题
Kotlin代码规范(一)_第1张图片
编译失败.png

kotlin给出的两种解决方案:

  1. on top level

    kotlin非常推荐的一种方式,也就是将静态常量的定义放到类的外面,不依赖类的而存在,如:
    top level.png
    既然可以不依赖类而存在,那么可以改成这样(public 关键字可以省略):
    Kotlin代码规范(一)_第2张图片
    取消类的定义.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。
Kotlin代码规范(一)_第3张图片
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 ~,真的是这样吗?肯定不是啊。
Kotlin代码规范(一)_第4张图片
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代码规范(一))