Kotlin語法糖總結(jié)

一、前言

使用kotlin開發(fā)項(xiàng)目已經(jīng)有一段時(shí)間,在使用kotlin的過程中,發(fā)現(xiàn)了許多很方便的語法糖,可以有效簡(jiǎn)潔代碼。在這里做個(gè)總結(jié)記錄,方便后續(xù)查閱。

二、kotlin基礎(chǔ)語法常識(shí)

1、 新建對(duì)象不需要new關(guān)鍵字。語句也不需要;結(jié)尾,但加上;也不會(huì)報(bào)錯(cuò)。

//java
StringBuffer buffer = new StringBuffer();

//kotlin
var buffer = StringBuffer()

2、var是kotlin保留字,用于聲明變量。與之相對(duì)的是val,表明引用不可變,但可以改變對(duì)象的值,使用varval聲明變量后,kotlin會(huì)根據(jù)上下文進(jìn)行類型推斷。

var name = ""
var age = 18

3、kotlin 用 : 取代了implementsextends保留字。

//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?) {
    }
}

4、kotlin的lambda也更加簡(jiǎn)約:

//正常情況
view.setOnClickListener({ v -> v.setVisibility(View.INVISIBLE) })

//當(dāng)lambda是函數(shù)的最后一個(gè)參數(shù)時(shí),可以將其移到括號(hào)外面
view.setOnClickListener() { v -> v.setVisibility(View.INVISIBLE) }

//當(dāng)函數(shù)只有一個(gè)lambda類型的參數(shù),可以去省去括號(hào)
view.setOnClickListener { v -> v.setVisibility(View.INVISIBLE) }

//當(dāng)lambda只有一個(gè)參數(shù),可省去參數(shù)列表,在表達(dá)式部分用it引用參數(shù)
view.setOnClickListener { it.setVisibility(View.INVISIBLE) }

5、所有定義了settergetter方法的字段,在kotlin中都可以通過賦值語法來直接操作

view.setOnClickListener { it.visibility = View.INVISIBLE }

6、空安全:kotlin中使用?.作為安全調(diào)用運(yùn)算符,將判空檢查和方法調(diào)用合并為一個(gè)操作。只有當(dāng)調(diào)用變量本身不為null時(shí),方法調(diào)用才成立,否則整個(gè)表達(dá)式返回null。

//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 getCountry(): String? {
    return person.company?.address?.country
}

//另外也可以通過在可空的后面通過?:指定為null時(shí)返回的默認(rèn)值
fun getCountry(): String {
    return person.company?.address?.country ?:""
}

7、擴(kuò)展函數(shù):擴(kuò)展函數(shù)是一個(gè)類的成員函數(shù),但它定義在類體外面。這樣定義的好處是,可以在任何時(shí)候任何地方給類添加功能。
在擴(kuò)展函數(shù)中,可以像類的其他成員函數(shù)一樣訪問類的屬性和方法(除了被private和protected修飾的成員)。還可以通過this引用類的實(shí)例,也可以省略它,把上段代碼進(jìn)一步簡(jiǎn)化:

fun Person.getCountry(): String? {
    return this.company?.address?.country
}

8、kotlin中常用的擴(kuò)展函數(shù)

函數(shù) 返回值 調(diào)用者角色 如何引用調(diào)用者
also 調(diào)用者本身 作為lambda參數(shù) it
apply 調(diào)用者本身 作為lambda接收者 this
let lambda返回值 作為lambda參數(shù) it
with lambda返回值 作為lambda接收者 this

apply舉例:

//java
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);

//kotlin
Intent(this,Activity1::class.java).apply {
    action = "actionA"
    putExtras(Bundle().apply {
        putString("content","hello")
        putString("sender","taylor")
    })
    startActivity(this)
}

also舉例:

//java
String var2 = "testLet";
int var4 = var2.length();
System.out.println(var4);
System.out.println(var2);

//kotlin
val result = "testLet".also {
    println(it.length)
}
println(result)

with舉例:

//java
@Override
public void onBindViewHolder(ViewHolder holder, int position) {

   ArticleSnippet item = getItem(position);
        if (item == null) {
            return;
        }
        holder.tvNewsTitle.setText(StringUtils.trimToEmpty(item.titleEn));
        holder.tvNewsSummary.setText(StringUtils.trimToEmpty(item.summary));
        String gradeInfo = "難度:" + item.gradeInfo;
        String wordCount = "單詞數(shù):" + item.length;
        String reviewNum = "讀后感:" + item.numReviews;
        String extraInfo = gradeInfo + " | " + wordCount + " | " + reviewNum;
        holder.tvExtraInfo.setText(extraInfo);
        ...
}

//kotlin
override fun onBindViewHolder(holder: ViewHolder, position: Int){
   val item = getItem(position)?: return
   
   with(item){
      holder.tvNewsTitle.text = StringUtils.trimToEmpty(titleEn)
       holder.tvNewsSummary.text = StringUtils.trimToEmpty(summary)
       holder.tvExtraInf.text = "難度:$gradeInfo | 單詞數(shù):$length | 讀后感: $numReviews"
       ...   
   
   }
}

let舉例:

//java
if(mContext!=null){
    textView.setText(mContext.getString(R.string.app_name))
    ...
}

//kotlin
mContext?.let{
    textView.setText(it.getString(R.string.app_name))
}

9、集合數(shù)據(jù)操作
kotlin中集合的操作符很多,此處只列舉使用頻率較高的幾個(gè),感興趣的可以看下這篇文章

  • forEach()forEachIndexed() 循環(huán)遍歷
val list = mutableListOf<String>().apply{
    add("A")
    add("B")
    add("C")
    add("D")
    add("E")
}
//遍歷
list.forEach{print(it)}
//遍歷帶角標(biāo)
list.forEachIndexed{content:String,index:Int -> print("$content $index")}
  • map() 在集合的每一個(gè)元素上應(yīng)用一個(gè)自定義的變化
list.map {
            it.apply {
                name = name.replace(name.first(), name.first().toUpperCase())
            }
        }
  • filter() 只保留滿足條件的集合元素
val reslutList = list.filter {it.length > 3}
  • toSet() 將集合元素去重

三、ktx擴(kuò)展包

Android KTX 是包含在 Android Jetpack 及其他 Android 庫中的一組 Kotlin 擴(kuò)展程序。KTX 擴(kuò)展程序可以為 Jetpack、Android 平臺(tái)及其他 API 提供簡(jiǎn)潔而慣用的 Kotlin代碼。下面舉例進(jìn)行說明:
1、Uri對(duì)象創(chuàng)建

//Kotlin創(chuàng)建一個(gè)Uri對(duì)象
 var s = "https://www.google.com"
 var uri = Uri.parse(s)

//使用Android KTX + Kotlin之后
 var s = "https://www.google.com".toUri()

2、SharedPreferences

//kotlin
sharedPreferences.edit().putBoolean(key, value).apply()
//Kotlin + Android KTX
sharedPreferences.edit { 
    putBoolean(key, value) 
}

3、Canvas操作

//kotlin
val pathDiffer = Path(mPath1).apply {
   op(mPath2, Path.Op.DIFFERENCE)
}

val mPaint = Paint()

canvas.apply {
     val checkpoint = save()
     translate(0F, 100F)
     drawPath(pathDiffer, mPaint)
     restoreToCount(checkpoint)
}
//Kotlin + Android KTX
val pathDiffer = mPath1 - mPath2
canvas.withTranslation(y = 100F) {
   drawPath(pathDiffer, mPaint)
}

4、OnPreDraw 回調(diào)

//kotlin
view.viewTreeObserver.addOnPreDrawListener(
       object : ViewTreeObserver.OnPreDrawListener {
           override fun onPreDraw(): Boolean {
               viewTreeObserver.removeOnPreDrawListener(this)
               actionToBeTriggered()
               return true
           }
       })
//Kotlin + Android KTX
view.doOnPreDraw { actionToBeTriggered() }

5、View的顯示與隱藏

//kotlin
view1.visibility = View.VISIBLE
view2.visibility = View.GONE
//Kotlin + Android KTX
view1.isVisible = true
view2.isVisible = false

6、Fragment事務(wù)

//kotlin
supportFragmentManager
    .beginTransaction()
    .add(R.id.content,Fragment1())
    .commit()
//Kotlin + Android KTX
supportFragmentManager.commit {
    add<Fragment1>(R.id.content)
}

7、ViewModel聲明

//kotlin
//共享范圍activity
val mViewMode1l = ViewModelProvider(requireActivity()).get(UpdateAppViewModel::class.java)
//共享范圍fragment 內(nèi)部
val mViewMode1l = ViewModelProvider(this).get(UpdateAppViewModel::class.java)
//Kotlin + Android KTX
//共享范圍activity
private val mViewModel by activityViewModels<MyViewModel>()
//共享范圍fragment 內(nèi)部
private val mViewModel by viewModel<MyViewModel>()

更多的KTX的代碼簡(jiǎn)化使用請(qǐng)直接查看官方文檔

四、合理利用擴(kuò)展屬性和擴(kuò)展方法

合理的利用kotlin的擴(kuò)展屬性和擴(kuò)展方法 ,可以減少一些重復(fù)代碼的書寫。下面通過兩個(gè)例子,來進(jìn)行說明:
1、將px值轉(zhuǎn)換成dp值
一般在項(xiàng)目中我們都定義了工具類,來進(jìn)行px轉(zhuǎn)dp的操作,不過在kotlin里我們可以使用擴(kuò)展屬性達(dá)到更簡(jiǎn)潔的實(shí)現(xiàn):

val Int.dp: Int
    get() {
        return TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP,
            this.toFloat(),
            Resources.getSystem().displayMetrics
        ).toInt()
    }

然后使用時(shí),就可以這樣做:

viewGroup.addView( textView, LayoutParam( 40.dp, 50.dp ) )

2、為集合添加打印方法
在開發(fā)過程中,經(jīng)常需要打印列表的內(nèi)容,通常我們會(huì)將集合進(jìn)行遍歷,然后打印數(shù)據(jù):

for (String str:list) {
    Log.v("test", "str="+str);
}

不同業(yè)務(wù)界面的數(shù)據(jù)類型不同,為了調(diào)試,這樣的 for 循環(huán)就會(huì)散落在各處,而且列表內(nèi)容會(huì)分若干條 log 輸出,中間極有可能被別的log打斷?,F(xiàn)在通過Kotlin的擴(kuò)展函數(shù)我們可以這樣做:

fun <T> Collection<T>.print(map: (T) -> String) =
    StringBuilder("\n[").also { sb ->
        //遍歷集合元素,通過 map 表達(dá)式將元素轉(zhuǎn)換成感興趣的字串,并獨(dú)占一行
        this.forEach { e -> sb.append("\n\t${map(e)},") }
        sb.append("\n]")
    }.toString()

為集合的基類Collection新增一個(gè)擴(kuò)展函數(shù),用于進(jìn)行打印,該方法中將集合內(nèi)容進(jìn)行遍歷,并使用StringBuilder拼接每個(gè)元素的內(nèi)容。
按照同樣思路,我們可以新增一個(gè)map的擴(kuò)展函數(shù):

fun <K, V> Map<K, V?>.print(map: (V?) -> String): String =
    StringBuilder("\n{").also { sb ->
        this.iterator().forEach { entry ->
            sb.append("\n\t[${entry.key}] = ${map(entry.value)}")
        }
        sb.append("\n}")
    }.toString()

在上面的兩個(gè)擴(kuò)展函數(shù)中,有用到kotlin的高階函數(shù),它是一種特殊的函數(shù),它的參數(shù)或者返回值是另一個(gè)函數(shù)。
反應(yīng)到上面的例子當(dāng)中就是,print()方法的入?yún)ap是一個(gè)函數(shù)。

五、自定義DSL

什么是DSL

DSL = domain specific language,即“特定領(lǐng)域語言”,與它對(duì)應(yīng)的一個(gè)概念叫“通用編程語言”,通用編程語言有一系列完善的能力來解決幾乎所有能被計(jì)算機(jī)解決的問題,像 Java 就屬于這種類型。而特定領(lǐng)域語言只專注于特定的任務(wù),比如 SQL 只專注于操縱數(shù)據(jù)庫,HTML 只專注于表述超文本。
既然通用編程語言能夠解決所有的問題,那為啥還需要特定領(lǐng)域語言?因?yàn)樗梢允褂帽韧ㄓ镁幊陶Z言中等價(jià)代碼更緊湊的語法來表達(dá)特定領(lǐng)域的操作。比如當(dāng)執(zhí)行一條 SQL 語句時(shí),不需要從聲明一個(gè)類及其方法開始。
簡(jiǎn)單來說,就是DSL更簡(jiǎn)潔。

舉個(gè)例子

當(dāng)我們需要組合兩個(gè)動(dòng)畫一起執(zhí)行的,并且在動(dòng)畫結(jié)束時(shí)展現(xiàn)視圖A,通常我們會(huì)這么實(shí)現(xiàn):

val span = 5000
AnimatorSet().apply {
    playTogether(
            ObjectAnimator.ofPropertyValuesHolder(
                    tvTitle,
                    PropertyValuesHolder.ofFloat("alpha", 0f, 1.0f),
                    PropertyValuesHolder.ofFloat("translationY", 0f, 100f)).apply {
                interpolator = AccelerateInterpolator()
                duration = span
            },
            ObjectAnimator.ofPropertyValuesHolder(
                    ivAvatar,
                    PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f),
                    PropertyValuesHolder.ofFloat("translationY", 0f,100f)).apply {
                interpolator = AccelerateInterpolator()
                duration = span
            }
    )
    addPauseListener(object :Animator.AnimatorPauseListener{
        override fun onAnimationPause(animation: Animator?) {
            Toast.makeText(context,"pause",Toast.LENGTH_SHORT).show()
        }

        override fun onAnimationResume(animation: Animator?) {
        }

    })
    addListener(object : Animator.AnimatorListener{
        override fun onAnimationRepeat(animation: Animator?) {
        }

        override fun onAnimationEnd(animation: Animator?) {
            showA()
        }

        override fun onAnimationCancel(animation: Animator?) {
        }

        override fun onAnimationStart(animation: Animator?) {
        }
    })
    start()
}

這一段apply()有點(diǎn)過長(zhǎng)了,嚴(yán)重降低了它的可讀性。罪魁禍?zhǔn)资?java 接口。雖然只用到接口中的一個(gè)方法,但卻必須將其余的方法保留空實(shí)現(xiàn)。這里可以利用自定義DSL來優(yōu)化。下面看怎么實(shí)現(xiàn)

1、新建類用于存放接口中各個(gè)方法的實(shí)現(xiàn)
class AnimatorListenerImpl {
    var onRepeat: ((Animator) -> Unit)? = null
    var onEnd: ((Animator) -> Unit)? = null
    var onCancel: ((Animator) -> Unit)? = null
    var onStart: ((Animator) -> Unit)? = null
}

它包含四個(gè)成員,每個(gè)成員的類型都是函數(shù)類型??匆幌?code>Animator.AnimatorListener的定義就能理解AnimatorListenerImpl的用意:

public static interface AnimatorListener {
    void onAnimationStart(Animator animation);
    void onAnimationEnd(Animator animation);
    void onAnimationCancel(Animator animation);
    void onAnimationRepeat(Animator animation);
}

該接口中的每個(gè)方法都接收一個(gè)Animator參數(shù)并返回空值,用 lambda可以表達(dá)成(Animator) -> Unit。所以AnimatorListenerImpl將接口中的四個(gè)方法的實(shí)現(xiàn)都保存在函數(shù)變量中,并且實(shí)現(xiàn)是可空的。

2、為 Animator 定義一個(gè)高階擴(kuò)展函數(shù)
fun AnimatorSet.addListener(action: AnimatorListenerImpl.() -> Unit) {
    AnimatorListenerImpl().apply { action }.let { builder ->
        //將回調(diào)實(shí)現(xiàn)委托給AnimatorListenerImpl的函數(shù)類型變量
        addListener(object : Animator.AnimatorListener {
            override fun onAnimationRepeat(animation: Animator?) {
                animation?.let { builder.onRepeat?.invoke(animation) }
            }

            override fun onAnimationEnd(animation: Animator?) {
                animation?.let { builder.onEnd?.invoke(animation) }
            }

            override fun onAnimationCancel(animation: Animator?) {
                animation?.let { builder.onCancel?.invoke(animation) }
            }

            override fun onAnimationStart(animation: Animator?) {
                animation?.let { builder.onStart?.invoke(animation) }
            }
        })
    }
}

Animator定義了擴(kuò)展函數(shù)addListener(),該函數(shù)接收一個(gè)lambdaaction。

3、使用自定義的 DSL 將本文開頭的代碼改寫:
val span = 5000
AnimatorSet().apply {
    playTogether(
            ObjectAnimator.ofPropertyValuesHolder(
                    tvTitle,
                    PropertyValuesHolder.ofFloat("alpha", 0f, 1.0f),
                    PropertyValuesHolder.ofFloat("translationY", 0f, 100f)).apply {
                interpolator = AccelerateInterpolator()
                duration = span
            },
            ObjectAnimator.ofPropertyValuesHolder(
                    ivAvatar,
                    PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f),
                    PropertyValuesHolder.ofFloat("translationY", 0f,100f)).apply {
                interpolator = AccelerateInterpolator()
                duration = span
            }
    )
    addPauseListener{
        onPause = { Toast.makeText(context,"pause",Toast.LENGTH_SHORT).show() }
    }
    addListener { 
        onEnd = { showA() } 
    }
    start()
}

上面代碼中省略了擴(kuò)展函數(shù)addPauseListener()的定義,它和addListener()是類似的。

六、總結(jié)

熟練掌握Kotlin語法糖,可以幫助我們簡(jiǎn)化代碼,節(jié)省開發(fā)時(shí)間,提高效率。一般配合Google提供的KTX庫即可完成大部分的項(xiàng)目開發(fā),熟練掌握擴(kuò)展函數(shù)和高階函數(shù)的使用更是能為代碼簡(jiǎn)化插上翅膀。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 集合創(chuàng)建 像創(chuàng)建一個(gè)數(shù)組一樣初始化一個(gè)含有默認(rèn)值的集合。避免了先創(chuàng)建,再賦值,這一點(diǎn)在java中是做不到的 普通集...
    YC_JS閱讀 1,968評(píng)論 1 2
  • Kotlin筆記 要理解Java與Kotlin的區(qū)別,就要從最根本的上來理解。Java是解釋型語言(邊解釋成二進(jìn)制...
    FFFSnow閱讀 1,153評(píng)論 0 0
  • 前言 從謹(jǐn)慎地在項(xiàng)目中引入kotlin到全部轉(zhuǎn)為kotlin開發(fā)我們用了大概半年的時(shí)間。這中間經(jīng)歷了從在一個(gè)小功能...
    申國(guó)駿閱讀 5,094評(píng)論 9 40
  • Kotlin 函數(shù): 示例: params: String 函數(shù)參數(shù) fun function(params: ...
    奔跑吧哈哈閱讀 3,461評(píng)論 0 13
  • 一、領(lǐng)域特定語言 DSL的概念 DSL(domain specific language),即領(lǐng)域?qū)S谜Z言:專門解...
    大鵬的鵬閱讀 876評(píng)論 0 0

友情鏈接更多精彩內(nèi)容