您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關研究學習Kotlin的方法有哪些,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
Kotlin是一門讓人感到很舒服的語言,相比Java來說,它更加簡潔,省去了瑣瑣碎碎的語法工作,同時了提供了類似Lambda,String template,Null Safe Operator等特性。讓開發者用起來得心應手。
普通的Java/Android程序員通常只需要很短的時間就能快速使用Kotlin。綜合Kotlin的諸多優點,加上Flipboard美國團隊自2015年已引入Kotlin,Flipboard中國團隊也已經開始采用Kotlin來作為Android主要開發語言。
雖然Kotlin使用簡單快捷,然而由于自己的深入研究的習慣導致每接觸到Kotlin的新功能,就馬不停蹄的研究它的本質,這里總結一下關于如何研究Kotlin的一些方法來快速研究掌握Kotlin。
到底研究什么
比如Kotlin中提供了一種類型叫做Object,使用它我們可以快速實現單例模式的應用。代碼特別的簡單
object AppSettings { }
那么問題來了,kotlin這個object類型的類是如何實現的呢,Null安全操作符的實現原理,Lambda表達式是基于內部類還是真正的Lambda,這些問題就是我們要研究的對象。
怎么研究
Kotlin和Java都是運行在JVM上,但是實際上JVM并不認識Java和Kotlin,因為它只和bytecode(即class文件)打交道。
因而通過研究bytecode,我們是可以了解Kotlin的一些深入原理的
由于同一份bytecode反編譯成java和kotlin文件是等價的,所以將kotlin編譯后的class文件反編譯成Java,也是具有參考和研究價值的。
實踐方法有哪些
利用Kotlin插件
利用kotlinc,javap等工具
一些實踐
Null Safe Operator實現原理
在Java中,我們經常會遇到空指針的問題,Kotlin特意增加了一個空指針安全操作符?。使用起來如下
fun testNullSafeOperator(string: String?) { System.out.println(string?.toCharArray()?.getOrNull(10)?.hashCode()) }
當我們進行這樣的調用時
testNullSafeOperator(null) testNullSafeOperator("12345678901") testNullSafeOperator("123")
得到的輸出結果為
null 49 null
從結果可見,并沒有像Java那樣拋出NullPointerException,而是遇到空指針則不繼續執行了。
那么Kotlin的這個空指針安全操作符是如何工作的呢,我們可以借助IntelliJ IDE的Kotlin插件來輔助我們研究,步驟如下
使用IntelliJ IDE打開一個待研究的Kotlin文件(需確保Kotlin插件已安裝)
按照下圖依次點擊至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 }
由字節碼分析可見,其實所謂的 空指針安全操作符其實內部就是以此判空來確保不出現空指針 ,如果字節碼不好理解,那我們使用上面的Decompile功能,將bytecode轉成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 } }
關于應用也很簡單
//在Kotlin文件中調用 AppSettings.updateConfig() //在Java文件中調用 AppSettings.INSTANCE.updateConfig();
我們先看一下AppSettings的字節碼文件
// ================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 //靜態代碼塊中實例化,即類加載時便開始實例化 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的單例模式的實現,在靜態代碼塊初始化實例。如果字節碼沒有看懂的話,可以嘗試反編譯成Java代碼來詳細研究。
Lambda表達式研究
除此之外,Kotlin也是支持了Lambda表達式的。由于并非所有的JVM版本都支持invokedynamic(Lambda表達式依賴的字節碼指令),比如Java 6的JVM,這其中就包含了許多安卓設備。所以我們懷疑Kotlin可能是像Scala那樣將lambda表達式轉換成了匿名內部類。
一個簡單的Lambda表達式例子
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; //這里就是使用了匿名內部類(常常包含$字符) 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 //生成的匿名內部類,規則為 當前的類名$當前的方法名$匿名內部類序號 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 }
分析字節碼可以看到有兩個class文件,因此可以推斷出Kotlin的Lambda表達式目前是一種基于內部類的語法糖實現。
除此之外,我們還可以使用kotlinc(Kotlin編譯器來驗證)
kotlinc Test.kt
執行完成后,查看生成的class文件
ls | grep ^Test Test$testObservable$1.class Test.class Test.kt
當然,我們還可以使用javap同樣實現查看bytecode的功能,即 javap -c className 。
除此之外,我們還可以利用上面的方法研究如下Kotlin的特性
lazy初始化
when表達式
方法引用
關于研究學習Kotlin的方法有哪些就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。