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给出的两种解决方案:
-
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的静态常量怎么调用啊,直接类名+.。所以上面的问题就迎刃而解了,知其然,知其所以然。
- 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,线程安全等等,这不是重点,贴个我觉得写得不错的链接吧。
下面主角来了,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 同样不展开说,想要了解的,同样贴一片文章
接下来,如果在单例中需要传递参数呢?比如常用的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 或者自行定义对象池进行优化。
说了这么多,看的也有点累了,敲黑板划重点了。
- 定义静态常量和静态方法,最好使用top level 方式,在java中调用时采用xxxKt的方式调用;kotlin中直接使用。
- 定义单例最好使用by lazy或者自定定义对象池进行优化。
- 慎用@JvmStatic注解(Double Method问题)
- 谨慎使用companion object(静态内部对象资源回收问题)
好了,结束。。耗时好久-_-||
网友评论