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)編譯問題
kotlin給出的兩種解決方案:
-
on top level
kotlin非常推薦的一種方式,也就是將靜態(tài)常量的定義放到類的外面,不依賴類的而存在,如:
既然可以不依賴類而存在,那么可以改成這樣(public 關(guān)鍵字可以省略):top level.png取消類的定義.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。
經(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)用啊,直接類名+.。所以上面的問題就迎刃而解了,知其然,知其所以然。
- 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ò)的鏈接吧。
下面主角來了,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 同樣不展開說,想要了解的,同樣貼一片文章
接下來,如果在單例中需要傳遞參數(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 ~,真的是這樣嗎?肯定不是啊。
聰明的你一定發(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)了。
- 定義靜態(tài)常量和靜態(tài)方法,最好使用top level 方式,在java中調(diào)用時(shí)采用xxxKt的方式調(diào)用;kotlin中直接使用。
- 定義單例最好使用by lazy或者自定定義對(duì)象池進(jìn)行優(yōu)化。
- 慎用@JvmStatic注解(Double Method問題)
- 謹(jǐn)慎使用companion object(靜態(tài)內(nèi)部對(duì)象資源回收問題)
好了,結(jié)束。。耗時(shí)好久-_-||

