Kotlin的解析(擴充)

春來江水綠如藍發表於2018-12-03

前言

  在前幾篇的基礎上,大家如果認真的閱讀,並跟著思路實踐的話,應該可以收穫很多的,前面基本已經覆蓋了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所屬範圍

  class  A{
        var A =13
        inner  class  B{
            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 : abc  num: 20
value : Bill    num: 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區別還是比較大的,這篇講的,實際開發中很是有用的,可能將的還是有限的,畢竟一些知識,還得在實踐的更深入的掌握

相關文章