Android開發中Kotlin之於java那些不一樣的功能

南山伐木發表於2018-09-05

感覺Kotlin對java不僅是一點點的改變,而是一種完全不同的體檢。習慣kotlin的簡潔後,就知道java到底e有多囉嗦了。今天簡單對比一下在Android開發中kotlin在語言本身上就有哪些好用的功能。

可觀察屬性Delegates.observable。在Oc中早已有的功能,非常方便在狀態值切換時使用,再也不怕狀態值改變時沒有呼叫到關聯的函式。

語法:

var max: Int by Delegates.observable(0) { property, oldValue, newValue -> 
  doSomething() 
}

複製程式碼

在初始化時接受兩個引數:初始值和修改時的處理程式。每當給屬性賦值後會自動呼叫該處理程。在處理狀態值時十分有用,不用再像Java裡那樣狀態值每重新賦值就需要呼叫一次更新函式。現在只需要在初始化時新增好更新函式即可。

如在tab頁切換時:

private var tabPosition: Int by Delegates.observable(0) { _: KProperty<*>, _: Int, tab: Int -> 
  if (tab == 0) { 
            tv_title_name.text = getString(R.string.title0)
            loadData0()
  ... 
        } else { 
            tv_title_name.text = getString(R.string.title1)
            loadData1()
  ... 
        }
    }

複製程式碼

在後面程式碼中,不用再關心tabPosition修改後的更新,只管重新賦值就好了。

適用場景:狀態切換,目標值監聽,屬性值改變時的聯動操作】

擴充套件。能夠擴充套件一個類的新功能而無需要繼承該類,也不用使用如裝飾器那樣的設計模式。直接寫直接引用。這是Oc中早已有的功能,而Java卻一直用不上。以致於在java程式碼中,會出現一堆如StringUtils、ToastUtils..等靜態工具類。使用時再將原物件當作引數傳入,做一堆操作後再返回,產生大量冗餘程式碼。有了擴充套件功能,能非常方便的對物件新增其他方法和使用程式碼封裝。

語法:

函式擴充套件:直接以fun 原類名.擴充套件函式名建立一個函式即可,在函式值中使用this來訪問原物件; 常用於對原物件功能的新增。如對常用的StringUtils的封裝,如判斷當前string是否為一個url:

fun String.isHttpUrl(): Boolean { 
  return !this.isEmpty() && (this.startsWith("http://") || this.startsWith("https://")) 
}

複製程式碼

在使用時直接呼叫str.isHttpUrl()即可【AndroidStudio會自動import】

屬性擴充套件:直接以val 屬性名:get()=...來建立即可。如使用Retrofit封裝一個Api物件:

val Api
  get() = 
        RetrofitManager.instance
                .retrofit(builder = DefaultRetrofitBuilder.builder)
                .create(Api::class.java)!!

複製程式碼

非空判斷,這個對於步步校驗,一路非空判斷的強迫症來說非常有用。用來擺脫Java中的xx==null,xx!=null的繁瑣的判斷。

  • ?.:相當於呼叫的if not null,如:id = item?.id,相當於if(item!=null) id=item.id else id=null,若想給個預設值,則可以使用:id = item?.id ?: 0
  • !!:非空斷言運算子(!!)將任何值轉換為非空型別,若該值為空則丟擲異常。如在必須非空時可使用Api.login(LoginReq(usename,pwd)).load(activity!!)
  • isNullOrEmpty()/isNullOrBlank()/isNotEmpty()/isNotBlank()/isEmpty()/isBlank():大量的內部函式更方便操作物件值。

資料類:常用於資料解析實體。在Java中要對網路響應的資料解析通常都是先定義好的響應的實體類,再進行解析。而資料類能更方便的定義實體類,並自動新增好set/get方法。再也不需要大篇大篇的set/get。

  • 語法:data class A(val a:Int);對複雜的響應實體可巢狀定義,如:
 data class NewsDetailResp(val data: NewsDetail) : BaseResponse() { 
        data class NewsDetail(val total: Double, 
  val detail: ArrayList<Detail>) { 
             data class Detail( 
  val id: Long, 
  val userid: Long 
                )
}
}

複製程式碼

lazy,延遲載入,這個在Java中也可通過延遲賦值來實現,但都沒有kotlin來的直接好用。

如單例模式的寫法:

 companion object { 
  val instance: UserManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { UserManager() } 
}

複製程式碼
  • 在Andoird開發中更好用的功能在於對釋放OnCreate()方法中的一大堆的初始化操作,如定義並初始化一個Adapter,可以直接在定義時賦值:
 private val adapter: AreaAdapter by lazy { AreaAdapter(this) } 

複製程式碼

【需注意,這種使用了上下文context的延遲載入,只能在生命週期方法中呼叫,不能在後面的定義的值時引用。】

告別方法過載:在Java開發中,對於同名不能同參的函式即方法過載。若引數過多,在封裝函式時會寫好幾個同名函式,造成大量程式碼冗餘。而在Kotlin中,有了預設引數的支援,只需要定義一個函式即可。如:

fun share(activity: Activity, url: String, title: String = "", desc: String = "", shareResult: (Boolean) -> Unit = {}) {} 

複製程式碼

lambda使用,無疑是對Java最期待而又用不上的功能,在Kotlin終於可以使用block當引數,省去了大量匿名內部類的建立。在Java中的回撥只能先定義一個介面,外部定義一個匿名內部類做一個引數傳入函式體,函式內部在做了一連串操作後再使用該引數執行。在Kotlin中只需定義一個block即可,如定義一個點選事件,並給個預設值:

  fun updateItem(item: Item, click: () -> Unit) {//沒有預設實現,必須傳入} 
  fun updateItem(item: Item, click: () -> Unit={}}) {//有空預設值,非必傳} 
  fun updateItem(item: Item, click: (Int) -> Boolean={}}) {//帶引數的block} 

  //儲存傳入的block 
  var click: () -> Unit={} 
  fun updateItem(item: Item, click: () -> Unit}) { 
        this.click = click
  //內部呼叫時使用 
        click.invoke()
    }
  //外部呼叫時直接使用 
    xx.updateItem(item){
  //click action 
    }

複製程式碼

list的foreach:對列表的遍歷,可完善代替for in的遍歷。如:

list.forEach { println("it = $it") }

複製程式碼

另外kotlin提供了大師的擴充套件函式用於對列表操作。如大家都知道在Java中不能在對一個List遍歷時動態刪除,否則會丟擲ConcurrentModificationException異常,一般會建一個臨時的list,儲存需要刪除的物件,在遍歷結束後再呼叫removeAll來實現。而在Kotlin中可直接在在遍歷中刪除:

list.filter { it -> it == "a" }.forEach { list.remove(it) }

複製程式碼

【另外Kotlin還新增對列表的排序(sort),轉map(associate),去重(distinct),過濾(filter),反轉(asReversed),查詢(find),最值(max/min),遞減(reduce),求和(sumBy/fold)等函式,非常方便集合的操作】

mapTo 動態初始化陣列:用於遍歷一個集合,並填充到另一個集合中。類似於Java下for迴圈遍歷集合,再將新的物件add到新的集合中。如遍歷響應資料填充座標集合:

  val entries = ArrayList<Entry>() 
    (start until datas.size).mapTo(entries) {
  val x = datas[it].time.toFloat() 
  val y = datas[it].value.toFloat() 
  Entry(x, y) 
    }

複製程式碼

with:引用一個例項,並呼叫多個方法。還可以在呼叫過程中做其他操作。如呼叫Retrofit例項:

val retrofit = with(Retrofit.Builder()) { 
  baseUrl(baseUrl) 
  addConverterFactory(builder.factory) 
            client(okHttpClient(builder))
            //do other something

            //rxJavaAdapter
  addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 
  build() 
        }

複製程式碼

apply/takeIf/also/takeUnless/let:常用的幾種擴充套件函式,非常方便對物件的判斷及操作,附上使用表格:

函式名 定義 block引數 閉包返回返回值 函式返回值 extension 其他
repeat fun repeat(times: Int, action: (Int) -> Unit) Unit Unit 普通函式
with fun with(receiver: T, f: T.() -> R): R = receiver.f() 無,可以使用this Any 閉包返回 普通函式
run run(block: () -> R): R Any 閉包返回 普通函式
let fun T.let(f: (T) -> R): R it Any 閉包返回
apply fun T.apply(f: T.() -> Unit): T 無,可以使用this Unit this
run fun T.run(f: T.() -> R): R 無,可以使用this Any 閉包返回
also fun T.also(block: (T) -> Unit): T it Unit 閉包返回
takeIf fun T.takeIf(predicate: (T) -> Boolean): T? it Boolean this 或 null 閉包返回型別必須是Boolean
takeUnless fun T.takeUnless(predicate: (T) -> Boolean): T? it Boolean this 或 null 閉包返回型別必須是Boolean

kotlinx對xml的註解解析。在Android最繁瑣莫過於對findViewById,也有很多的第三方框架如butterknife來的簡化這部分程式碼。但在使用了kotlinx後才發現定義元件的物件都是多餘的。只須在根專案下引用gradle外掛:**classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version",**在xml定義了一個元件後,在程式碼時直接使用id來就可以了,根本不需要定義變數,再findViewById去賦值。如在xml中定義:

  <?xml version="1.0" encoding="utf-8"?> 
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  xmlns:app="http://schemas.android.com/apk/res-auto" 
  android:layout_width="match_parent" 
  android:layout_height="wrap_content"> 

  <TextView 
  android:id="@+id/tv_name" 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:layout_marginStart="16dp" 
  android:text="yoyo" 
  android:textColor="@color/text_black" 
  android:textSize="@dimen/text_16" 
  app:layout_constraintBottom_toBottomOf="parent" 
  app:layout_constraintLeft_toLeftOf="parent" 
  app:layout_constraintTop_toTopOf="parent" /> 

</android.support.constraint.ConstraintLayout>

複製程式碼

在Activity中使用setContentView引用了該xml後,則可以直接使用tv_name.text="jim"這樣的程式碼一賦值。若在自定義的view,需要init{}程式碼塊中呼叫infalte函式引入xml資源,在如:

class ResultView(context: Context) : ConstraintLayout(context) { 
    init {
        inflate(context, R.layout.view_result, this) 
    }

    fun addListener(clickAction: () -> Unit) { 
        btn_result.setOnClickListener { clickAction.invoke() } 
    }
}

複製程式碼

相關文章