Kotlin語法糖總結(jié)

一、前言

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

二篙悯、kotlin基礎(chǔ)語法常識

1蚁阳、 新建對象不需要new關(guān)鍵字。語句也不需要;結(jié)尾鸽照,但加上;也不會報錯螺捐。

//java
StringBuffer buffer = new StringBuffer();

//kotlin
var buffer = StringBuffer()

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

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也更加簡約:

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

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

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

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

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

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

6脾拆、空安全:kotlin中使用?.作為安全調(diào)用運算符馒索,將判空檢查和方法調(diào)用合并為一個操作。只有當(dāng)調(diào)用變量本身不為null時名船,方法調(diào)用才成立绰上,否則整個表達式返回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時返回的默認(rèn)值
fun getCountry(): String {
    return person.company?.address?.country ?:""
}

7渠驼、擴展函數(shù):擴展函數(shù)是一個類的成員函數(shù)蜈块,但它定義在類體外面。這樣定義的好處是,可以在任何時候任何地方給類添加功能百揭。
在擴展函數(shù)中爽哎,可以像類的其他成員函數(shù)一樣訪問類的屬性和方法(除了被private和protected修飾的成員)。還可以通過this引用類的實例器一,也可以省略它课锌,把上段代碼進一步簡化:

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

8、kotlin中常用的擴展函數(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中集合的操作符很多渺贤,此處只列舉使用頻率較高的幾個,感興趣的可以看下這篇文章

  • 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() 在集合的每一個元素上應(yīng)用一個自定義的變化
list.map {
            it.apply {
                name = name.replace(name.first(), name.first().toUpperCase())
            }
        }
  • filter() 只保留滿足條件的集合元素
val reslutList = list.filter {it.length > 3}
  • toSet() 將集合元素去重

三请毛、ktx擴展包

Android KTX 是包含在 Android Jetpack 及其他 Android 庫中的一組 Kotlin 擴展程序志鞍。KTX 擴展程序可以為 JetpackAndroid 平臺及其他 API 提供簡潔而慣用的 Kotlin代碼方仿。下面舉例進行說明:
1固棚、Uri對象創(chuàng)建

//Kotlin創(chuàng)建一個Uri對象
 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的代碼簡化使用請直接查看官方文檔

四面徽、合理利用擴展屬性和擴展方法

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

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

然后使用時戴差,就可以這樣做:

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

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

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

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

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

為集合的基類Collection新增一個擴展函數(shù),用于進行打印照卦,該方法中將集合內(nèi)容進行遍歷式矫,并使用StringBuilder拼接每個元素的內(nèi)容。
按照同樣思路役耕,我們可以新增一個map的擴展函數(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()

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

五惯悠、自定義DSL

什么是DSL

DSL = domain specific language,即“特定領(lǐng)域語言”竣况,與它對應(yīng)的一個概念叫“通用編程語言”克婶,通用編程語言有一系列完善的能力來解決幾乎所有能被計算機解決的問題,像 Java 就屬于這種類型丹泉。而特定領(lǐng)域語言只專注于特定的任務(wù)情萤,比如 SQL 只專注于操縱數(shù)據(jù)庫,HTML 只專注于表述超文本摹恨。
既然通用編程語言能夠解決所有的問題筋岛,那為啥還需要特定領(lǐng)域語言?因為它可以使用比通用編程語言中等價代碼更緊湊的語法來表達特定領(lǐng)域的操作晒哄。比如當(dāng)執(zhí)行一條 SQL 語句時睁宰,不需要從聲明一個類及其方法開始。
簡單來說寝凌,就是DSL更簡潔柒傻。

舉個例子

當(dāng)我們需要組合兩個動畫一起執(zhí)行的,并且在動畫結(jié)束時展現(xiàn)視圖A,通常我們會這么實現(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()有點過長了较木,嚴(yán)重降低了它的可讀性红符。罪魁禍?zhǔn)资?java 接口。雖然只用到接口中的一個方法伐债,但卻必須將其余的方法保留空實現(xiàn)预侯。這里可以利用自定義DSL來優(yōu)化。下面看怎么實現(xiàn)

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

它包含四個成員雌桑,每個成員的類型都是函數(shù)類型∽娼瘢看一下Animator.AnimatorListener的定義就能理解AnimatorListenerImpl的用意:

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

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

2膏斤、為 Animator 定義一個高階擴展函數(shù)
fun AnimatorSet.addListener(action: AnimatorListenerImpl.() -> Unit) {
    AnimatorListenerImpl().apply { action }.let { builder ->
        //將回調(diào)實現(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定義了擴展函數(shù)addListener(),該函數(shù)接收一個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()
}

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

六沮榜、總結(jié)

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市查乒,隨后出現(xiàn)的幾起案子弥喉,更是在濱河造成了極大的恐慌,老刑警劉巖玛迄,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件由境,死亡現(xiàn)場離奇詭異,居然都是意外死亡憔晒,警方通過查閱死者的電腦和手機藻肄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拒担,“玉大人,你說我怎么就攤上這事攻询〈雍常” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵钧栖,是天一觀的道長低零。 經(jīng)常有香客問我,道長拯杠,這世上最難降的妖魔是什么掏婶? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮潭陪,結(jié)果婚禮上雄妥,老公的妹妹穿的比我還像新娘最蕾。我一直安慰自己,他們只是感情好老厌,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布瘟则。 她就那樣靜靜地躺著,像睡著了一般枝秤。 火紅的嫁衣襯著肌膚如雪醋拧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天淀弹,我揣著相機與錄音丹壕,去河邊找鬼剪侮。 笑死焕窝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的忿磅。 我是一名探鬼主播痊焊,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼盏袄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了薄啥?” 一聲冷哼從身側(cè)響起辕羽,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎垄惧,沒想到半個月后刁愿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡到逊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年铣口,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片觉壶。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡脑题,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铜靶,到底是詐尸還是另有隱情叔遂,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布争剿,位于F島的核電站已艰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蚕苇。R本人自食惡果不足惜哩掺,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涩笤。 院中可真熱鬧嚼吞,春花似錦盒件、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至呢蔫,卻和暖如春切心,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背片吊。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工绽昏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俏脊。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓全谤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親爷贫。 傳聞我的和親對象是個殘疾皇子认然,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容

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