一個減法的故事:Kotlin 擴展函數 ,Operator 和 性能優化

前言編程

在寫自定義控件的時候,有時會須要對PointF對象進行必定操做,計算兩個點之間的水平間距和垂直間距。app

簡化需求也就是要算出兩個點之間的差值。函數

用代碼實現大概是這樣的優化

fun minusPoint(p1: PointF, p2: PointF): PointF {
    val dx = p1.x - p2.x
    val dy = p1.y - p2.y
    return PointF(dx, dy)
}

//使用
val p = minusPoint(p1,p2)
複製代碼

第一次修改this

這樣的寫法太Java了,由於咱們用到的是Kotlin,咱們能夠改爲這樣spa

fun minusPoint(p1: PointF, p2: PointF): PointF = PointF(p1.x - p2.x, p1.y - p2.y)

//使用
val p = minusPoint(p1,p2)
複製代碼

第二次修改code

固然,這樣也不夠好。咱們使用Kotlin的擴展函數爲PointF這個對象添加上一個擴展函數對象

fun PointF.minusPoint(p2: PointF): PointF = PointF(this.x - p2.x, this.y - p2.y)

//使用
val p = p1.minusPoint(p2)
複製代碼

這樣的調用看起來可讀性高了很是多。ci

第三次修改io

由於PointF自帶了offset的方法

public final void offset(float dx, float dy) {
  x += dx;
  y += dy;
}
複製代碼

因此咱們將能夠改爲這個樣子

fun PointF.minusPoint(p2: PointF): PointF = PointF().apply {
    this.offset(-p2.x, -p2.y)
}

//使用
val p = p1.minusPoint(p2)
複製代碼

第四次修改

有編程經驗的小夥伴可能從第一次就發現了這個函數的一個「問題」,就是每次都會建立一個新的PointF對象。因此咱們還能夠對它進行一次「優化」

fun PointF.minusPoint(p2: PointF): PointF = this.apply {
    this.offset(-p2.x, -p2.y)
}

//使用
val p = p1.minusPoint(p2)
複製代碼

這樣每次調用都不會產生新的對象,直接使用原來的對象就能夠了。一切都看起來很美妙。

第五次修改

咱們再次回到咱們一開始的時候,咱們一開始須要解決的問題是「計算兩個點的差值」,那麼從語義上來說。是否是能夠簡單的描述成爲這樣

val p1: Point
val p2: Point
val p = p1 - p2 
複製代碼

瞭解Kotlin 的operator的同窗可能從第一次看到需求的時候就想到了-操做符。

很明顯 ktx中就有PointF的擴展操做符。

/**
 * Offsets this point by the negation of the specified point and returns the result
 * as a new point.
 */
inline operator fun PointF.minus(p: PointF): PointF {
    return PointF(x, y).apply {
        offset(-p.x, -p.y)
    }
}

//使用
val p = p1 - p2
複製代碼

再一次被Kotlin 甜到 !

第六次修改

細心的朋友發現,這個擴展操做符每次都返回了一個新的對象。

那是否是ktx這個函數寫的很差?

其實不是這樣的,如今回到咱們的第四次的「優化」。

fun PointF.minusPoint(p2: PointF): PointF = this.apply {
    this.offset(-p2.x, -p2.y)
}

//使用
val p = p1.minusPoint(p2)
複製代碼

如今咱們來考慮一個問題,咱們使用了p1對象減去p2的得到了一個對象p ,這時p其實就是p1,而它們的屬性此時已被改變。若是這時,再去使用p1去作一些其餘操做,顯然就和預期獲得的結果不同了。

發現問題所在了嗎?咱們的優化「減法」改變被減數,這顯然是不合理的。

因此咱們在第五次的修改是不太合理的,可是我又不想用第六次的方案,由於它的確額外的對象,我就是餓死,死在外面,也不會吃這個語法糖的?!。

那麼應該怎麼辦呢?

上面咱們說到,一個減法是不該該去改變被減數的,減法獲得的值理所固然是一個新的值。

那麼是否咱們就只能這樣了呢?固然不是,咱們再次回到咱們的需求,「得到兩個點之間的差值」,其實這句需求還能夠再增長完善一些,「得到兩個點之間的差值,爲了避免產生新的對象能夠直接修改其中一個點的值」

那麼到這裏能夠發現,咱們有一個很是合適的操做符來描述它,也就是 -=

直接來上代碼

inline operator fun PointF.minusAssign(p: PointF) {
    this.apply {
        offset(-p.x, -p.y)
    }
}

//使用,沒有返回值
p -= p2
複製代碼

btw,因爲傳入的參數不是函數類型,這裏的inline是多餘的。

因爲沒有返回值,那麼咱們能夠這樣調用

val p1 = p.apply {
    this -= center
}
複製代碼

至此,咱們的減法就算完成了。

經過這個減法,我獲得了什麼?

  • 瞭解了kotlin的operator寫法
  • 瞭解了kotlin的inline的一些規則
  • 函數若是會對傳入參數進行修改,須要謹慎是否真的應該這樣作。
相關文章
相關標籤/搜索