白話文轉文言文——Kotlin程式碼簡潔之道

唐子玄發表於2019-05-02

這是kotlin系列文章的第一篇。這個系列記錄的是kotlin使用感受,其中也會穿插基礎知識點,並通過專案實戰程式碼綜合運用這些知識點。

剛接觸kotlin就被它的簡潔震撼到了(kotlin的作者一定是一個極簡主義!)。一起來看下kotlin是怎麼通過“斷舍離”來實現簡潔的:

new 分號 型別

新建物件不需要new關鍵詞。

任何語句的結尾不需要; 但加上也不會有語法錯誤。

//java
StringBuffer buffer = new StringBuffer();

//kotlin
var buffer = StringBuffer()
複製程式碼
  • varkotlin保留字,用於宣告變數。與之對應的是val用於宣告常量。
  • 不需要顯示指明變數型別,因為kotlin會根據上下文推斷變數型別,這種能力稱為 “型別推導”
  • 可以通過下面的語法來指定型別:
var buffer: StringBuffer = StringBuffer()
複製程式碼
  • kotlin中型別是後置的,在變數名後跟上: 型別就可以顯示指定型別。同理,它也用於指定函式返回值型別:
fun getMessage(): String{
    return "message"
}
複製程式碼
  • fun關鍵字用於宣告函式。

implements extends @Override

//java
public class CheckableActivity extends Activity {
    final public void setStatus(){}
}

public class MyListener implements View.OnClickListener{

    @Override
    public void onClick(View v) {
    }
}

//kotlin
class CirclePartyListActivity : Activity() {
    fun setStatus(){}
}

class MyListener : View.OnClickListener{
    override fun onClick(v: View?) {
    }
}
複製程式碼
  • kotlin: 取代了implementsextends保留字。

  • @Override也被override保留字取代並且和函式頭同行,kotlin中的override是必須的,而java中是可選的。

  • kotlin中類和方法預設是final的(可省略不寫),這意味著預設情況下,類和方法是不允許被繼承和重寫的(這是為了防止脆弱的基類,即對基類方法的修改會導致子類出現預期之外的行為)。只有通過open保留字顯示宣告該類或方法可以被繼承或重寫:

open class A{
    open fun do(){
    }
}
複製程式碼

()

kotlinlambda也更加簡約:

//正常情況
view.setOnClickListener({ v -> v.setVisibility(View.INVISIBLE) })
//當lambda是函式的最後一個引數時,可以將其移到括號外面
view.setOnClickListener() { v -> v.setVisibility(View.INVISIBLE) }
//當函式只有一個lambda型別的引數,可以去省去括號
view.setOnClickListener { v -> v.setVisibility(View.INVISIBLE) }
//當lambda只有一個引數,可省去引數列表,在表示式部分用it引用引數
view.setOnClickListener { it.setVisibility(View.INVISIBLE) }
複製程式碼

getter setter

java中,欄位和其訪問器的組合被稱為屬性,kotlin引入了property access syntax,它取代了欄位和訪問器方法,用這種方式進一步簡化上面的程式碼:

view.setOnClickListener { it.visibility = View.INVISIBLE }
複製程式碼
  • 所有被定義了gettersetter方法的欄位,在kotlin中都可以通過賦值語法來操作。

{ } return

kotlin中的語句和表示式的唯一區別是:表示式有值,而語句沒有。如果函式體由單個表示式構成,可以省去花括號和return,並用賦值的=表示將表示式的值賦值給返回值:

//java
public int add(int a, int b){
    return a+b ;
}

//kotlin
fun add(a: Int, b: Int): Int = a+b
複製程式碼

在 lambda 表示式中包含多條語句或表示式時,若省略return,則預設將最後一個表示式的值作為返回值:

view.setOnTouchListener { v, event ->
    ...//do something
    false
}
複製程式碼

上述程式碼表示在OnTouchListener.onTouch()中返回 false。

switch-case-break

//java
String color;
switch(colorInt){
    case Color.RED:
        color = "red";
        break;
    case Color.BLUE:
        color = "blue";
        break;
    default:
        color = "black";
        break;
}

//kotlin
val color = when (colorInt) {
    Color.RED -> "red"
    Color.BLUE -> "blue"
    else -> "black"
}
複製程式碼
  • when用於取代switch-case,不需要在每個分支末尾呼叫break,如果有一個分支命中則會立即返回。
  • when是一個表示式,這意味著它有返回值,返回值等於命中分支中最後一條語句的返回值。

default

java中的default保留字用於介面中預設方法的實現。在kotlin中可以省去它。

//java
public interface IMessage {
    default String getMessage() {
        return "default message";
    }

    int getMessageId();
}

//kotlin
interface IMessage {
    fun getMessage(): String {
        return "default message"
    }

    fun getMessageId(): Int
}
複製程式碼
  • Intjava中基本資料型別int的包裝類,kotlin中沒有基本資料型別。

防禦式程式設計

//java
public class Address {
    private String country;
    public String getCountry() {
        return country;
    }
}

public class Company {
    private Address address;
    public Address getAddress() {
        return address;
    }
}

public class Person {
    private Company company;
    public String getCountry() {
        String country = null;
        //多次防禦式程式設計
        if (company != null) {
            if (company.getAddress() != null) {
                country = company.getAddress().getCountry();
            }
        }
        return country;
    }
}

//kotlin
fun Person.getCountry(): String? {
    return this.company?.address?.country
}
複製程式碼
  • ?.稱為 安全呼叫運算子 ,它把判空檢查和一次方法呼叫合併成一個操作。只有當呼叫變數不為null時,才會執行呼叫,否則整個表示式返回null。這意味著,不再需要防禦式程式設計。
  • ?置於型別之後表示這個型別可空,上面的函式宣告表示此函式的返回值可能為null
  • 上面的 kotlin 程式碼為Person類新增了一個getCountry()方法,這種技術叫擴充套件函式

擴充套件函式

擴充套件函式是一個類的成員函式,但它定義在類體外面。這樣定義的好處是,可以在任何時候任何地方給類新增功能。

在擴充套件函式中,可以像類的其他成員函式一樣訪問類的屬性和方法(除了被privateprotected修飾的成員)。還可以通過this引用類的例項,也可以省略它,把上段程式碼進一步簡化:

fun Person.getCountry(): String? {
    return company?.address?.country
}
複製程式碼

kotlin預定了很多擴充套件函式,下面就會用到其中的apply

冗餘物件名

程式設計中經常會遇到“對同一個物件做多次操作”的場景,比如:

Intent intent = new Intent(this, Activity1.class);
intent.setAction("actionA");
Bundle bundle = new Bundle();
bundle.putString("content","hello");
bundle.putString("sender","taylor");
intent.putExtras(bundle);
startActivity(intent);
複製程式碼

其中,物件intentbundle重複出現若干次,這對於極簡主義的kotlin來說不能忍,它的表達方式如下:

Intent(this,Activity1::class.java).apply {
    action = "actionA"
    putExtras(Bundle().apply {
        putString("content","hello")
        putString("sender","taylor")
    })
    startActivity(this)
}
複製程式碼

其中,apply的定義如下:

//為泛型T物件新增新功能apply(),它接受一個lambda型別的引數block,且lambda呼叫的發起者是物件本身
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    //執行lambda
    block()
    //返回撥用者自身
    return this
}
複製程式碼

對於object.apply{lambda}可以簡單的理解為:在object物件上應用lambda操作,並且最終返回object物件本身。所以上述程式碼也可以寫成更加緊湊的形式:

startActivity(Intent(this, Activity1::class.java).apply {
    action = "actionA"
    putExtras(Bundle().apply {
        putString("content", "hello")
        putString("sender", "taylor")
    })
})
複製程式碼

同一型別的預定義擴充套件函式還包括withletalso。它們的共同點是適用於 “對同一個物件做多次操作” 的場景 。它們的不同點總結如下:

函式 返回值 呼叫者角色 如何引用呼叫者
also 呼叫者本身 作為lambda引數 it
apply 呼叫者本身 作為lambda接收者 this
let lambda返回值 作為lambda引數 it
with lambda返回值 作為lambda接收者 this
  • kotlin中,發起呼叫擴充套件函式的那個物件,叫接收者物件。同理,發起呼叫lambda的物件叫做lambda接收者
  • 可以將also的原始碼和apply做對比,更好的理解他們呼叫者角色的差別:
//為泛型T物件新增新功能also(),它接受一個lambda型別的引數block,且物件是lambda的引數
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}
複製程式碼

綜合應用

“讓 app 中所有被點選的 View 都帶上縮放動畫”。綜合運用上述kotlin知識點實現這個需求之前,先來看看java是如何實現的:

  1. 先定義工具類,該工具類為傳入的View分別設定觸控和單擊監聽器。在按下時播放動畫,鬆手時反向播放動畫。
public class ViewUtil {
    public static void addExtraAnimClickListener(View view, ValueAnimator animator, View.OnClickListener listener) {
        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        animator.start();
                        break;
                    case MotionEvent.ACTION_CANCEL:
                    case MotionEvent.ACTION_UP:
                        animator.reverse();
                        break;
                }
                //若返回true,則遮蔽了點選事件
                return false;
            }
        });

        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (listener != null) {
                    listener.onClick(v);
                }
            }
        });
    }
}
複製程式碼
  1. 在介面中新建動畫和點選響應邏輯並將它們傳遞給工具類
Button btn3 = findViewById(R.id.btn3);
//新建動畫:變大
ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 1.2f);
animator.setDuration(100);
animator.setInterpolator(new AccelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Float value = ((Float) animation.getAnimatedValue());
        btn3.setScaleX(value);
        btn3.setScaleY(value);
    }
});
//點選響應邏輯
View.OnClickListener onClickListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(Activity1.this, "spring anim", Toast.LENGTH_LONG).show();
    }
};
//應用工具類
ViewUtil.addExtraAnimClickListener(btn3, animator, onClickListener);
複製程式碼

不要眨眼,換kotlin閃亮登場:

  1. 給View新增擴充套件函式
//擴充套件函式接收一個動畫和一個點選響應邏輯(用lambda表示)
fun View.extraAnimClickListener(animator: ValueAnimator, action: (View) -> Unit) {
    setOnTouchListener { v, event ->
        when (event.action) {
            MotionEvent.ACTION_DOWN -> animator.start()
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> animator.reverse()
        }
        false
    }
    setOnClickListener { action(this) }
}
複製程式碼
  1. 應用擴充套件函式
btnSpringAnim.extraAnimClickListener(ValueAnimator.ofFloat(1.0f, 1.15f).apply {
    interpolator = AccelerateInterpolator()
    duration = 100
    addUpdateListener {
        btnSpringAnim.scaleX = it.animatedValue as Float
        btnSpringAnim.scaleY = it.animatedValue as Float
    }
}) { Toast.makeText(this, "spring anim", Toast.LENGTH_LONG).show() }
複製程式碼
  • btnSpringAnim是一個Button控制元件的id(只要裝了kotlin外掛,就不需要findViewById())
  • as保留字用於型別強制轉換。
  • 是不是有一種 “白話文轉文言文” 的感覺,kotlin憑藉著極強的表達力用將近 1/3 的程式碼量完成了功能。

知識點總結

  • var保留詞用於宣告變數,val保留詞用於宣告常量。大多數情況下不需要顯示指明變數型別,kotlin 具有型別推導能力,會根據上下文自動推斷型別。
  • fun保留字用於宣告函式。
  • override保留字表示重寫父類方法或者實現介面中的抽象方法,與 java 不同的是,它必須顯示出現在重寫方法前( java 允許省略)。
  • as保留字用於型別強制轉換。
  • kotlin 中型別是後置的,在變數名或函式引數列表後跟上: 型別就可以顯示指定型別。
  • :還用於繼承類(取代extends)、實現介面(取代implements)。
  • 新建物件時不需要new,而是直接呼叫建構函式。
  • 語句末尾不需要; 但加上也不會有語法錯誤。
  • kotlin 中類和方法預設是final的,他們不能被繼承和重寫。只有通過加上open後才能被繼承和重寫。
  • kotlin 中沒有基本資料型別,而是用其對應的包裝類表示。
  • 給介面方法新增預設實現時不需要default關鍵字。
  • kotlin 中的語句和表示式的唯一區別是:表示式有值,而語句沒有。
  • 如果函式體由單個表示式構成,可以省去花括號和return。
  • when保留字用於取代switch-case,而且它是一個表示式,返回值是命中分支中最後一表示式的值。
  • kotlin 引入了property access syntax,不再需要getter和setter方法,可以直接對屬性賦值。
  • ?.稱為 安全呼叫運算子 ,只有當呼叫變數不為null時,才會執行呼叫,否則整個表示式返回null。這樣就避免了防禦式程式設計。
  • ?置於型別之後表示這個型別的變數或返回值值可能為null
  • kotlin 使用擴充套件函式,可以在類體外給類新增方法。
  • kotlin 預定了很多擴充套件函式,其中有一類適用於“對同一個物件做多次操作”。包括also()apply()let()with()

最近開始學習 kotlin ,研讀《Kotlin實戰》的同時,在專案中加以運用。這個系列會不斷地新增來自書本和實踐中的新發現。希望對你能有所幫助~~

相關文章