前言
在前幾篇的基礎上,大家如果認真的閱讀,並跟著思路實踐的話,應該可以收穫很多的,前面基本已經覆蓋了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區別還是比較大的,這篇講的,實際開發中很是有用的,可能將的還是有限的,畢竟一些知識,還得在實踐的更深入的掌握