Kotlin的解析(拓展)
在前幾篇的基礎上,大家如果認真的閱讀,並跟著思路實踐的話,應該可以收穫很多的,前面基本已經覆蓋了Kotlin語言中常見的使用方法,下面讓我們來進一步,在前面的基礎上深深的擴充套件一下
1. Kotlin的技術拓展其一
儘管到目前為止,我們已經講了很多關於Kotlin的新技術,但遠遠是不夠的,讓我們進一步瞭解更多的Kotlin的新知識
1.1 資料結構與集合
1.1.1 資料結構
所謂的資料結構,就是將物件中的資料解析成相應的獨立變數,也就是脫離原來的物件存在
data class Person(var name:String, var age :Int,var salary:Float) var person = Person("Bill",30,120f) var (name,age,salary)=person //資料解構 Log.i("tag",name+age+salary) 輸出 Bill20120 複製程式碼
有很多的物件,可以儲存一組值,並可以通過for...in的語句,解構出值
var map = mutableMapOf<Int,String>() map.put(10,"Devin") map.put(20,"Max") for ((key,values) in map){ Log.d("tag",key.toString() +";;;;"+values) } 輸出 10;;;;Devin 20;;;;Max //其中這些物件都是通過資料類實現的,當然我們自己也可以實現的,這裡就不做展示了,自己可以下去試試 複製程式碼
1.1.2 集合
儘管Kotlin可以使用JDK中提供的集合,但Kotlin標準庫也提供了自己的集合,與之不同的是,Kotlin提供的集合分為可修改和不可修改的,這一點和Apple的CocoaTouch類似。在Kotlin只讀包括LIst、Set、Map;可寫的包括MutableList、MutableSet、MutableMap等
public interface List<out E> : Collection<E> { ... } public interface Set<out E> : Collection<E> { ... } public interface Map<K, out V> { ... } 很顯然上面的都是out修飾的,前面學的out宣告,泛型如果使用了,那麼該泛型就能只用於讀操作 val nums = mutableListOf<Int>(1,2,3) var reNums :List<Int> = nums; nums.add(4)//可以增加;reNums只能讀取 複製程式碼
從這個程式碼可以看出,集合並沒有提供構造器建立集合物件,提供了一些函式來建立
listOf; setOf; mapOf; mutableListOf; mutableSetOf; mutableMapOf
val nums = mutableListOf<Int>(1,2,3) var toList = nums.toList()//通過此方法可以把讀寫的專為只讀的 var toMutableList = toList.toMutableList()//只讀的也可以轉為讀寫的 複製程式碼
1.2 範圍值
1.2.1 值範圍的應用
值範圍表示式用rangTo函式實現,該函式的操作形式是(..),相關的操作符in和!in
var n =20 if(n in 1..10){ Log.d("tag","滿足條件") } if (n !in 30..80){ Log.d("tag","滿足條件") } 複製程式碼
整數的值範圍(IntRange、LongRange、CharRange)還有一種額外的功能,就是可以對這些值範圍進行遍歷。編譯器會負責將這些程式碼轉換為Java中基於下標的for迴圈,不會產生不必要的效能損耗
for(i in 1..10){ Log.i("tag",i.tostring()) } //相當於Java中的 //for(int i=1; I<=10;i++) for(i in 10..1){ //如果按照倒序的話,什麼都不會輸出的 Log.i("tag",i.tostring()) } //但是非要按照倒序輸出,只要使用標準庫中的downTo函式就可以了 for(i in 10 downTo 1){ Log.i("tag",i*i) //輸出100到1共10個數 } //在前面的程式碼中,i的順序加1或減1,也就步長為1;如果要是改變步長的話,可以使用step函式 for(i in 1..10 step 2){ Log.i("tag",i.toString()) } 輸出:1,3,5,7,9 //在前面的程式碼,使用的範圍都是閉區間,要是這種的形式[1,10) for(i in 1 until 10){ //不包含10的 Log.i("tag",i.toString()) } 複製程式碼
1.2.2 常用工具函式
(1)rangTo,整數型別上定義的rangTo操作符,只是簡單地呼叫*Rang類的建構函式
class Int { public operator fun rangeTo(other: Int): IntRange public operator fun rangeTo(other: Long): LongRange } 複製程式碼
(2)downTo,擴充套件函式可以用於一對整數型別,下面就是通過擴充套件函式新增的downTo函式
public infix fun Long.downTo(to: Long): LongProgression { return LongProgression.fromClosedRange(this, to, -1L) } public infix fun Byte.downTo(to: Long): LongProgression { return LongProgression.fromClosedRange(this.toLong(), to, -1L) } 複製程式碼
(3)reversed,對於每個*Progression類都定義了reversed擴充套件函式,所有的這些函式都會返回相反的數列
public fun IntProgression.reversed(): IntProgression { return IntProgression.fromClosedRange(last, first, -step) } 複製程式碼
(4)對於每個*Progression類都定義了step擴充套件函式,所有這些函式都會返回使用新的step值,步長值引數要求永遠是整數,因此這個函式不會改變數列遍歷的方向
public infix fun IntProgression.step(step: Int): IntProgression { if (!isPositive) throw IllegalArgumentException("Step must be positive, was: $step.") return IntProgression.fromClosedRange(first, last, if (this.step > 0) step else -step) } 複製程式碼
注意:函式返回的數列last值可能與原始數列的last的值不同,這是為了保證(last-first)%increment==0原則
1.3 型別檢查與型別轉換
1.3.1 is 與 !is操作符
var obj: Any = 234 if (obj is String) { } if (obj is Int){ } if (obj !is Int){ } 複製程式碼
如果is表示式滿足條件,Kotlin編譯器會自動轉換is前面的物件到後面的資料型別
var obj: Any = 234 if (obj is Int){ obj.rangeTo(4)//Int型別才有的,自動轉換了 } //注意的是,物件is後面型別要相容,如果不相容的話,無法編譯通過 var obj = 234 if (obj is String) {//編譯不過 obj.rangeTo(4) } 複製程式碼
1.3.2 智慧型別轉換
var a :Any = "max" //&&的右側已經轉換成了string if (a is String && a.length>0){ } // ||的右側也已經轉換為string if (a !is String ||a.length<0){ } //這種型別的轉換對於when和while同樣有效果的 var x :Any ="sfs" when(x){ is Int -> Log.i("tag", (x+1).toString()) is String ->Log.i("tag", x.length.toString()) } 複製程式碼
1.3.3 強行型別轉換
如果型別強制轉換,而且型別不相容,型別轉換操作符通常會丟擲一個異常,稱之為不安全的,而不安全的型別轉換使用中綴操作符as
var a :Any ="max" val x :Int = a as Int//java.lang.ClassCastException //為了避免丟擲異常,我們可以使用安全的型別轉換操作符 as?,當型別轉換失敗時,它會返回null var a :Any? =null val x : Int? = a as Int? Log.i("tag", x.toString())// null var a :Any? ="max" val x : Int? = a as? Int? //as後面也要加?不然還是會拋異常 Log.i("tag", x.toString())// null 複製程式碼
1.3.4 this表示式
為了訪問外層範圍內的this,我們使用this@lable,其中@lable是一個標籤,代表this所屬範圍
classA{ var A =13 innerclassB{ fun Int.foo(){ val a =this@A //指向A的this val b = this@B //指向B的this val c =this//指向foo()函式接收者,一個Int值 val d =this@foo //指向foo()函式接收者,一個Int值 val funLit = { s:String -> val e = this//指向foo()函式接收者,因為包含當前程式碼的Lambda表示式沒有接收者 } } } } 複製程式碼
2. Kotlin的技術拓展其二
本章將會繼續探索null值安全性、異常類、註解以及反射 #####2.1 null值安全性 在Java中,經常遇到空指標的困擾,表腦瓜子疼,對於這個Kotlin使用一些新的語法糖,會盡可能避免null異常帶來的麻煩
2.1.1 可為null與不可為null型別
var a:String =null//編譯錯誤,不能為null var b:String = "abc" b=null//編譯錯誤,不能為null 複製程式碼
要允許null值,我們可以將變數宣告為null的字串型別:String ?
var a :String ="abcd" var b:String? = "abc" b =null var len = a.length//由於a不允許為null,因此不會產生NPE val len1 = b.length //編譯出錯,因為b可能為null //要是必須訪問的話,使用if語句進行判斷 var len = if (b==null) -1 else b.length; //第二種就是使用安全呼叫操作符:? print(b?.length) //輸出為null //當然可以使用在類中的呼叫 bob?.depart?.head?.name //這樣的鏈式呼叫,只有屬性鏈中任何一個屬性為null,整個表示式就會返回null 複製程式碼
2.1.3 Elvis操作符
假設我們有一個可為null的引用r,我們可以認為:如果不為空,就是用,否則使用其他的值
//如果"?:"左側的表示式不是null,Elvis操作符就會返回它的值,否則,返回右側表示式的值,注意,只有在左側表示式為null,才會計算右側表示式的值 var len1 = b?.length ?:-1 複製程式碼
在Kotlin中,由於throw和return都是表示式,因此可以用在右側
var len1 = b?.length ?:throw NullPointerException() var len2 = b?.length ?:return 複製程式碼
2.1.4 !!操作符
對於NPE的忠實粉絲,還可以寫!!b,對於b不為null的情況,這個表示式會返回一個非null的值,如果是null,就會丟擲NPE
var len2 = b!!.length 複製程式碼
#####2.2 異常類 Kotlin中所有的異常類都是Throwable的子類,要丟擲異常,可以使用throw表示式
//和Java的使用區別不是太大,這裡就不說了 try { } catch (e: NullPointerException) { null } finally { } 複製程式碼
2.3 註解(Annotations)
註解是用來為程式碼新增元資料(metadata)的一種手段,要宣告一個註解,需要在類之前新增annotation修飾符
annotation class Fancy
註解的其他屬性,可以通過向註解類新增元註解(meta-annotation)的方式指定 (1)@Target 指定這個註解可被用於哪些元素(類、函式、屬性和表示式) (2)@Retention指定這個註解的資訊是否被儲存到編譯後class檔案中,以及在執行時是否可以通過反射訪問到它(預設情況下,這兩個設定都是true) (3)@Repetable允許在單個元素上多次使用同一註解 (4)@MustBeDoucumented表示這個註解是公開API的一部分,在自動產生的API文件的類或者函式簽名中,應該包含這個註解的資訊
@Target(AnnotationTarget.CLASS ,AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.SOURCE) @MustBeDocumented @Repeatable annotation class MyAnnotationClass{ } 複製程式碼
2.3.1 使用註解
註解可以在類、函式、函式引數和函式返回值中使用
@ MyAnnotationClass class Foo { @ MyAnnotationClass fun bazz(@MyAnnotationClass foo : Int):Int{ return (@MyAnnotationClass l) } } //如果需要對一個類的主構造器加註解,那麼必須在主構造器宣告中新增constructor關鍵字,然後在這個關鍵字之前添加註解 class Foo @MyAnnotationClass constructor(n:Int){ // ... } 複製程式碼
2.3.2 註解類的構造器
註解類可以擁有帶引數的構造器
annotation class Special(val why :String)
使用Special("example") class Foo{}
並不是所有型別的引數都允許在註解類的構造器中使用,註解構造器只允許使用下面型別的引數 (1)與Java基本型別對應的資料型別(Int、Long) (2)String(3)列舉類(4)KClass (5)其他註解類
2.4 反射(Reflection)
儘管Kotlin是基於JVM的程式語言,但在Kotlin中使用反射,需要引用額外的庫(kotlin-reflect.jar)
2.4.1 類引用
val c = MyClass::class 類引用是一個KClass型別的值
注意,Kotlin的類引用不是一個Java的類引用,要得到Java的類引用,可使用KClass物件例項的java屬性
val c =MyClass::class.java
2.4.2 列舉類成員
反射最常用的功能之一就是列舉的成員,如類的屬性、方法等。
class Person(val name :String,val num :Int){ fun process(){ } } var c = Person :: class // 獲取Person類中所有的成員列表(屬性和函式) println("成員數:" +c.members.size) for(member in c.members){ //輸出每個成員的名字和返回型別 print(member.name +"" +member.returnType) println() } //獲取Person類中所有屬性的個數 println("屬性個數:" +c.memberProperties.size) //列舉Person類中所有的屬性 for(property in c.memberProperties){ //輸出當前屬性的名字和返回型別 print(property.name+""+property.returnType) println() } //獲取Person類中所有函式的個數 println("函式個數:"+c.memberFunctions.size) for(function in c.memberFunctions){ //輸出當前函式的名字和返回型別 println(function.name+" " +function.returnType) } 執行這段程式碼,會輸出如下內容 成員數:6 num kotlin.Int value kotlin.String process kotlin.Unit equals kotlin.Boolean hashCode kotlin.Int toString kotlin.String 屬性個數:2 num kotlin.Int value kotlin.String 函式個數:4 process kotlin.Unit equals kotlin.Boolean hashCode kotlin.Int toString kotlin.String 複製程式碼
2.4.3 動態呼叫成員函式
反射的另外一個重要應用就是可以動態呼叫物件的成員,如成員函式、成員函式、成員屬性,所謂的動態呼叫,就是根據成員名字進行呼叫,可以動態指定成員的名字,通過::操作符,可以直接返回類的成員
class Person(val name:String ,val num:Int){ fun process(){ println("name:${value}num:${num}") } } // 獲取process函式物件 var p = Person::process // 呼叫invoke函式執行process函式 p.invoke(person("abc",20)) //利用Java的反射機制指定process方法名字 var method= Person::class.java.getMethod("process") //動態呼叫process函式 method.invoke(Person("Bill",30)) 輸出: name : abcnum: 20 value : Billnum: 30 複製程式碼
2.4.4 動態呼叫成員屬性
Kotlin類的屬性與函式一樣,也可以使用反射動態呼叫,不過Kotlin編譯器在處理Kotlin類屬性時,會將器轉換為getter和setter方法,而不是與屬性同名的Java欄位。
class Person { var name :String = "Devin" get() = field set(v){ field = v } } 複製程式碼
很明顯,name屬性變成了getName和setName方法,因此,在使用反射技術訪問Kotlin屬性時,仍然需按成員函式處理,如果使用Java的反射技術,仍然要使用getMethod方法獲取getter和setter方法物件,而不能使用getField方法獲取欄位
class Person { var name :String = "Devin" get() = field set(v){ field = v } } var person= Person() //獲得屬性物件 var name = Person::name // 讀取屬性值 println(name.get(person)) // 設定屬性值 name.set(person,"Mike") println(name.get(person)) //無法使用getField方法獲得name欄位值,因為根本就沒生成name欄位,只有getName和setName方法 var field = Person::class.java.getField("name") field.set(person,"Json") println(field.get(person)) //利用Java反射獲取getName方法 var getName = Person::class.java.getMethod("getName") //利用 Java反射獲取SetName方法,注意,getMethod方法的第2個引數可變的 //需要傳遞setName引數型別的class //這裡不能指定Kotlin中的String,而要指定java.lang.String var setName = Person::class.java.getMethod("setName",java.lang.String().javaClass) //動態設定name屬性的值 setName.invoke(person,"John") //動態獲取name屬性的值 println(getName.invoke(person)) 複製程式碼
總結
通過這一篇,更深入的瞭解kotlina的更多新的知識,以及語法糖,和Java區別還是比較大的,這篇講的,實際開發中很是有用的,可能將的還是有限的,畢竟一些知識,還得在實踐的更深入的掌握