研究學(xué)習(xí)Kotlin的一些方法
Kotlin是一門讓人感到很舒服的語言,相比Java來說,它更加簡潔,省去了瑣瑣碎碎的語法工作,同時了提供了類似Lambda,String template,Null Safe Operator等特性。讓開發(fā)者用起來得心應(yīng)手。
普通的Java/Android程序員通常只需要很短的時間就能快速使用Kotlin。綜合Kotlin的諸多優(yōu)點(diǎn),加上Flipboard美國團(tuán)隊(duì)自2015年已引入Kotlin,F(xiàn)lipboard中國團(tuán)隊(duì)也已經(jīng)開始采用Kotlin來作為Android主要開發(fā)語言。
雖然Kotlin使用簡單快捷,然而由于自己的深入研究的習(xí)慣導(dǎo)致每接觸到Kotlin的新功能,就馬不停蹄的研究它的本質(zhì),這里總結(jié)一下關(guān)于如何研究Kotlin的一些方法來快速研究掌握Kotlin。
到底研究什么
比如Kotlin中提供了一種類型叫做Object,使用它我們可以快速實(shí)現(xiàn)單例模式的應(yīng)用。代碼特別的簡單
- object AppSettings {
 - }
 
那么問題來了,kotlin這個object類型的類是如何實(shí)現(xiàn)的呢,Null安全操作符的實(shí)現(xiàn)原理,Lambda表達(dá)式是基于內(nèi)部類還是真正的Lambda,這些問題就是我們要研究的對象。
怎么研究
- Kotlin和Java都是運(yùn)行在JVM上,但是實(shí)際上JVM并不認(rèn)識Java和Kotlin,因?yàn)樗缓蚥ytecode(即class文件)打交道。
 - 因而通過研究bytecode,我們是可以了解Kotlin的一些深入原理的
 - 由于同一份bytecode反編譯成java和kotlin文件是等價的,所以將kotlin編譯后的class文件反編譯成Java,也是具有參考和研究價值的。
 
實(shí)踐方法有哪些
- 利用Kotlin插件
 - 利用kotlinc,javap等工具
 
一些實(shí)踐
Null Safe Operator實(shí)現(xiàn)原理
在Java中,我們經(jīng)常會遇到空指針的問題,Kotlin特意增加了一個空指針安全操作符?。使用起來如下
- fun testNullSafeOperator(string: String?) {
 - System.out.println(string?.toCharArray()?.getOrNull(10)?.hashCode())
 - }
 
當(dāng)我們進(jìn)行這樣的調(diào)用時
- testNullSafeOperator(null)
 - testNullSafeOperator("12345678901")
 - testNullSafeOperator("123")
 
得到的輸出結(jié)果為
- null
 - 49
 - null
 
從結(jié)果可見,并沒有像Java那樣拋出NullPointerException,而是遇到空指針則不繼續(xù)執(zhí)行了。
那么Kotlin的這個空指針安全操作符是如何工作的呢,我們可以借助IntelliJ IDE的Kotlin插件來輔助我們研究,步驟如下
- 使用IntelliJ IDE打開一個待研究的Kotlin文件(需確保Kotlin插件已安裝)
 - 按照下圖依次點(diǎn)擊至Show Kotlin Bytecode
 
- 上面的步驟操作后,會得到這樣的bytecode
 
- // access flags 0x19
 - public final static testNullSafeOperator(Ljava/lang/String;)V
 - @Lorg/jetbrains/annotations/Nullable;() // invisible, parameter 0
 - L0
 - LINENUMBER 11 L0
 - GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
 - ALOAD 0
 - DUP
 - IFNULL L1 //對string字符串判空
 - INVOKESTATIC kotlin/text/StringsKt.toCharArray (Ljava/lang/String;)[C
 - DUP
 - IFNULL L1 //對CharArray判空
 - BIPUSH 10
 - INVOKESTATIC kotlin/collections/ArraysKt.getOrNull ([CI)Ljava/lang/Character;
 - DUP
 - IFNULL L1 //對Char判空
 - INVOKEVIRTUAL java/lang/Object.hashCode ()I
 - INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
 - GOTO L2
 - L1
 - POP
 - ACONST_NULL
 - L2
 - INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
 - L3
 - LINENUMBER 12 L3
 - RETURN
 - L4
 - LOCALVARIABLE string Ljava/lang/String; L0 L4 0
 - MAXSTACK = 3
 - MAXLOCALS = 1
 - }
 
由字節(jié)碼分析可見,其實(shí)所謂的 空指針安全操作符其實(shí)內(nèi)部就是以此判空來確保不出現(xiàn)空指針 ,如果字節(jié)碼不好理解,那我們使用上面的Decompile功能,將bytecode轉(zhuǎn)成Java,如圖操作
反編譯后得到的Java代碼為
- public static final void testNullSafeOperator(@Nullable String string) {
 - PrintStream var10000;
 - Integer var5;
 - label18: {
 - var10000 = System.out;
 - if(string != null) {
 - PrintStream var2 = var10000;
 - if(string == null) {
 - throw new TypeCastException("null cannot be cast to non-null type java.lang.String");
 - }
 - char[] var4 = ((String)string).toCharArray();
 - Intrinsics.checkExpressionValueIsNotNull(var4, "(this as java.lang.String).toCharArray()");
 - char[] var3 = var4;
 - var10000 = var2;
 - if(var3 != null) {
 - Character var10001 = ArraysKt.getOrNull(var3, 10);
 - if(var10001 != null) {
 - var5 = Integer.valueOf(var10001.hashCode());
 - break label18;
 - }
 - }
 - }
 - var5 = null;
 - }
 - var10000.println(var5);
 - }
 
這樣讀起來是不是更加容易理解呢。
Object類型研究
這里我們回到Object類型,還是再舉個例子看看如何使用
- //這是定義
 - object AppSettings {
 - fun updateConfig() {
 - //do some updating work
 - }
 - }
 
關(guān)于應(yīng)用也很簡單
- //在Kotlin文件中調(diào)用
 - AppSettings.updateConfig()
 - //在Java文件中調(diào)用
 - AppSettings.INSTANCE.updateConfig();
 
我們先看一下AppSettings的字節(jié)碼文件
- // ================AppSettings.class =================
 - // class version 50.0 (50)
 - // access flags 0x31
 - public final class AppSettings {
 - // access flags 0x11
 - public final updateConfig()V
 - L0
 - LINENUMBER 7 L0
 - RETURN
 - L1
 - LOCALVARIABLE this LAppSettings; L0 L1 0
 - MAXSTACK = 0
 - MAXLOCALS = 1
 - // access flags 0x2
 - private <init>()V
 - L0
 - LINENUMBER 4 L0
 - ALOAD 0
 - INVOKESPECIAL java/lang/Object.<init> ()V
 - ALOAD 0
 - CHECKCAST AppSettings
 - PUTSTATIC AppSettings.INSTANCE : LAppSettings;
 - RETURN
 - L1
 - LOCALVARIABLE this LAppSettings; L0 L1 0
 - MAXSTACK = 1
 - MAXLOCALS = 1
 - // access flags 0x19
 - public final static LAppSettings; INSTANCE
 - // access flags 0x8
 - static <clinit>()V
 - L0
 - LINENUMBER 4 L0
 - //靜態(tài)代碼塊中實(shí)例化,即類加載時便開始實(shí)例化
 - NEW AppSettings
 - INVOKESPECIAL AppSettings.<init> ()V
 - RETURN
 - MAXSTACK = 1
 - MAXLOCALS = 0
 - @Lkotlin/Metadata;(mv={1, 1, 5}, bv={1, 0, 1}, k=1, d1={"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0008\u0002\n\u0002\u0010\u0002\n\u0000\u0008\u00c6\u0002\u0018\u00002\u00020\u0001B\u0007\u0008\u0002\u00a2\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004\u00a8\u0006\u0005"}, d2={"LAppSettings;", "", "()V", "updateConfig", "", "production sources for module KotlinObject"})
 - // compiled from: AppSettings.kt
 - }
 
由此可見,Kotlin的object也就是Java的單例模式的實(shí)現(xiàn),在靜態(tài)代碼塊初始化實(shí)例。如果字節(jié)碼沒有看懂的話,可以嘗試反編譯成Java代碼來詳細(xì)研究。
Lambda表達(dá)式研究
除此之外,Kotlin也是支持了Lambda表達(dá)式的。由于并非所有的JVM版本都支持invokedynamic(Lambda表達(dá)式依賴的字節(jié)碼指令),比如Java 6的JVM,這其中就包含了許多安卓設(shè)備。所以我們懷疑Kotlin可能是像Scala那樣將lambda表達(dá)式轉(zhuǎn)換成了匿名內(nèi)部類。
一個簡單的Lambda表達(dá)式例子
- class Test {
 - fun testObservable() {
 - val observable = Observable()
 - observable.addObserver { o, arg ->
 - System.out.println("$o $arg")
 - }
 - }
 - }
 
我們使用插件同樣查看bytecode
- // ================Test.class =================
 - // class version 50.0 (50)
 - // access flags 0x31
 - public final class Test {
 - // access flags 0x11
 - public final testObservable()V
 - L0
 - LINENUMBER 8 L0
 - NEW java/util/Observable
 - DUP
 - INVOKESPECIAL java/util/Observable.<init> ()V
 - ASTORE 1
 - L1
 - LINENUMBER 9 L1
 - ALOAD 1
 - GETSTATIC Test$testObservable$1.INSTANCE : LTest$testObservable$1; //這里就是使用了匿名內(nèi)部類(常常包含$字符)
 - CHECKCAST java/util/Observer
 - INVOKEVIRTUAL java/util/Observable.addObserver (Ljava/util/Observer;)V
 - L2
 - LINENUMBER 12 L2
 - RETURN
 - L3
 - LOCALVARIABLE observable Ljava/util/Observable; L1 L3 1
 - LOCALVARIABLE this LTest; L0 L3 0
 - MAXSTACK = 2
 - MAXLOCALS = 2
 - // access flags 0x1
 - public <init>()V
 - L0
 - LINENUMBER 6 L0
 - ALOAD 0
 - INVOKESPECIAL java/lang/Object.<init> ()V
 - RETURN
 - L1
 - LOCALVARIABLE this LTest; L0 L1 0
 - MAXSTACK = 1
 - MAXLOCALS = 1
 - @Lkotlin/Metadata;(mv={1, 1, 5}, bv={1, 0, 1}, k=1, d1={"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0008\u0002\n\u0002\u0010\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004\u00a8\u0006\u0005"}, d2={"LTest;", "", "()V", "testObservable", "", "production sources for module KotlinObject"})
 - // access flags 0x18
 - final static INNERCLASS Test$testObservable$1 null null
 - // compiled from: Space.kt
 - }
 - // ================Test$testObservable$1.class =================
 - // class version 50.0 (50)
 - // access flags 0x30
 - //生成的匿名內(nèi)部類,規(guī)則為 當(dāng)前的類名$當(dāng)前的方法名$匿名內(nèi)部類序號
 - final class Test$testObservable$1 implements java/util/Observer {
 - // access flags 0x11
 - public final update(Ljava/util/Observable;Ljava/lang/Object;)V
 - L0
 - LINENUMBER 10 L0
 - GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
 - NEW java/lang/StringBuilder
 - DUP
 - INVOKESPECIAL java/lang/StringBuilder.<init> ()V
 - ALOAD 1
 - INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder;
 - LDC " "
 - INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
 - ALOAD 2
 - INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder;
 - INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
 - INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
 - L1
 - LINENUMBER 11 L1
 - RETURN
 - L2
 - LOCALVARIABLE this LTest$testObservable$1; L0 L2 0
 - LOCALVARIABLE o Ljava/util/Observable; L0 L2 1
 - LOCALVARIABLE arg Ljava/lang/Object; L0 L2 2
 - MAXSTACK = 3
 - MAXLOCALS = 3
 - // access flags 0x0
 - <init>()V
 - ALOAD 0
 - INVOKESPECIAL java/lang/Object.<init> ()V
 - RETURN
 - MAXSTACK = 1
 - MAXLOCALS = 1
 - // access flags 0x19
 - public final static LTest$testObservable$1; INSTANCE
 - // access flags 0x8
 - static <clinit>()V
 - NEW Test$testObservable$1
 - DUP
 - INVOKESPECIAL Test$testObservable$1.<init> ()V
 - PUTSTATIC Test$testObservable$1.INSTANCE : LTest$testObservable$1;
 - RETURN
 - MAXSTACK = 2
 - MAXLOCALS = 0
 - @Lkotlin/Metadata;(mv={1, 1, 5}, bv={1, 0, 1}, k=3, d1={"\u0000\u0016\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\u0008\u0002\n\u0002\u0010\u0000\n\u0000\u0010\u0000\u001a\u00020\u00012\u000e\u0010\u0002\u001a\n \u0004*\u0004\u0018\u00010\u00030\u00032\u000e\u0010\u0005\u001a\n \u0004*\u0004\u0018\u00010\u00060\u0006H\n\u00a2\u0006\u0002\u0008\u0007"}, d2={"<anonymous>", "", "o", "Ljava/util/Observable;", "kotlin.jvm.PlatformType", "arg", "", "update"})
 - OUTERCLASS Test testObservable ()V
 - // access flags 0x18
 - final static INNERCLASS Test$testObservable$1 null null
 - // compiled from: Space.kt
 - }
 
分析字節(jié)碼可以看到有兩個class文件,因此可以推斷出Kotlin的Lambda表達(dá)式目前是一種基于內(nèi)部類的語法糖實(shí)現(xiàn)。
除此之外,我們還可以使用kotlinc(Kotlin編譯器來驗(yàn)證)
- kotlinc Test.kt
 
執(zhí)行完成后,查看生成的class文件
- ls | grep ^Test
 - Test$testObservable$1.class
 - Test.class
 - Test.kt
 
當(dāng)然,我們還可以使用javap同樣實(shí)現(xiàn)查看bytecode的功能,即 javap -c className 。
除此之外,我們還可以利用上面的方法研究如下Kotlin的特性
- lazy初始化
 - when表達(dá)式
 - 方法引用
 
關(guān)于Kotlin的研究方法目前就是這些,Kotlin很簡單,但也要知其所以然,方能游刃有余編碼。希望大家可以嘗試Kotlin,并玩的開心。

















 
 
 

 
 
 
 