Kotlin语法糖总结

一、前言

使用kotlin开发项目已经有一段时间,在使用kotlin的过程中,发现了许多很方便的语法糖,可以有效简洁代码。在这里做个总结记录,方便后续查阅。

二、kotlin基础语法常识

1、 新建对象不需要new关键字。语句也不需要;结尾,但加上;也不会报错。

//java
StringBuffer buffer = new StringBuffer();

//kotlin
var buffer = StringBuffer()

2、var是kotlin保留字,用于声明变量。与之相对的是val,表明引用不可变,但可以改变对象的值,使用varval声明变量后,kotlin会根据上下文进行类型推断。

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) })

//当lambda是函数的最后一个参数时,可以将其移到括号外面
view.setOnClickListener() { v -> v.setVisibility(View.INVISIBLE) }

//当函数只有一个lambda类型的参数,可以去省去括号
view.setOnClickListener { v -> v.setVisibility(View.INVISIBLE) }

//当lambda只有一个参数,可省去参数列表,在表达式部分用it引用参数
view.setOnClickListener { it.setVisibility(View.INVISIBLE) }

5、所有定义了settergetter方法的字段,在kotlin中都可以通过赋值语法来直接操作

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

6、空安全:kotlin中使用?.作为安全调用运算符,将判空检查和方法调用合并为一个操作。只有当调用变量本身不为null时,方法调用才成立,否则整个表达式返回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时返回的默认值
fun getCountry(): String {
    return person.company?.address?.country ?:""
}

7、扩展函数:扩展函数是一个类的成员函数,但它定义在类体外面。这样定义的好处是,可以在任何时候任何地方给类添加功能。
在扩展函数中,可以像类的其他成员函数一样访问类的属性和方法(除了被private和protected修饰的成员)。还可以通过this引用类的实例,也可以省略它,把上段代码进一步简化:

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

8、kotlin中常用的扩展函数

函数 返回值 调用者角色 如何引用调用者
also 调用者本身 作为lambda参数 it
apply 调用者本身 作为lambda接收者 this
let lambda返回值 作为lambda参数 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 = "单词数:" + 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 | 单词数:$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、集合数据操作
kotlin中集合的操作符很多,此处只列举使用频率较高的几个,感兴趣的可以看下这篇文章

  • forEach()forEachIndexed() 循环遍历
val list = mutableListOf<String>().apply{
    add("A")
    add("B")
    add("C")
    add("D")
    add("E")
}
//遍历
list.forEach{print(it)}
//遍历带角标
list.forEachIndexed{content:String,index:Int -> print("$content $index")}
  • map() 在集合的每一个元素上应用一个自定义的变化
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对象创建

//Kotlin创建一个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 回调

//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事务

//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 内部
val mViewMode1l = ViewModelProvider(this).get(UpdateAppViewModel::class.java)
//Kotlin + Android KTX
//共享范围activity
private val mViewModel by activityViewModels<MyViewModel>()
//共享范围fragment 内部
private val mViewModel by viewModel<MyViewModel>()

更多的KTX的代码简化使用请直接查看官方文档

四、合理利用扩展属性和扩展方法

合理的利用kotlin的扩展属性和扩展方法 ,可以减少一些重复代码的书写。下面通过两个例子,来进行说明:
1、将px值转换成dp值
一般在项目中我们都定义了工具类,来进行px转dp的操作,不过在kotlin里我们可以使用扩展属性达到更简洁的实现:

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、为集合添加打印方法
在开发过程中,经常需要打印列表的内容,通常我们会将集合进行遍历,然后打印数据:

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

不同业务界面的数据类型不同,为了调试,这样的 for 循环就会散落在各处,而且列表内容会分若干条 log 输出,中间极有可能被别的log打断。现在通过Kotlin的扩展函数我们可以这样做:

fun <T> Collection<T>.print(map: (T) -> String) =
    StringBuilder("\n[").also { sb ->
        //遍历集合元素,通过 map 表达式将元素转换成感兴趣的字串,并独占一行
        this.forEach { e -> sb.append("\n\t${map(e)},") }
        sb.append("\n]")
    }.toString()

为集合的基类Collection新增一个扩展函数,用于进行打印,该方法中将集合内容进行遍历,并使用StringBuilder拼接每个元素的内容。
按照同样思路,我们可以新增一个map的扩展函数:

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()

在上面的两个扩展函数中,有用到kotlin的高阶函数,它是一种特殊的函数,它的参数或者返回值是另一个函数。
反应到上面的例子当中就是,print()方法的入参map是一个函数。

五、自定义DSL

什么是DSL

DSL = domain specific language,即“特定领域语言”,与它对应的一个概念叫“通用编程语言”,通用编程语言有一系列完善的能力来解决几乎所有能被计算机解决的问题,像 Java 就属于这种类型。而特定领域语言只专注于特定的任务,比如 SQL 只专注于操纵数据库,HTML 只专注于表述超文本。
既然通用编程语言能够解决所有的问题,那为啥还需要特定领域语言?因为它可以使用比通用编程语言中等价代码更紧凑的语法来表达特定领域的操作。比如当执行一条 SQL 语句时,不需要从声明一个类及其方法开始。
简单来说,就是DSL更简洁。

举个例子

当我们需要组合两个动画一起执行的,并且在动画结束时展现视图A,通常我们会这么实现:

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()有点过长了,严重降低了它的可读性。罪魁祸首是 java 接口。虽然只用到接口中的一个方法,但却必须将其余的方法保留空实现。这里可以利用自定义DSL来优化。下面看怎么实现

1、新建类用于存放接口中各个方法的实现
class AnimatorListenerImpl {
    var onRepeat: ((Animator) -> Unit)? = null
    var onEnd: ((Animator) -> Unit)? = null
    var onCancel: ((Animator) -> Unit)? = null
    var onStart: ((Animator) -> Unit)? = null
}

它包含四个成员,每个成员的类型都是函数类型。看一下Animator.AnimatorListener的定义就能理解AnimatorListenerImpl的用意:

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

该接口中的每个方法都接收一个Animator参数并返回空值,用 lambda可以表达成(Animator) -> Unit。所以AnimatorListenerImpl将接口中的四个方法的实现都保存在函数变量中,并且实现是可空的。

2、为 Animator 定义一个高阶扩展函数
fun AnimatorSet.addListener(action: AnimatorListenerImpl.() -> Unit) {
    AnimatorListenerImpl().apply { action }.let { builder ->
        //将回调实现委托给AnimatorListenerImpl的函数类型变量
        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定义了扩展函数addListener(),该函数接收一个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()
}

上面代码中省略了扩展函数addPauseListener()的定义,它和addListener()是类似的。

六、总结

熟练掌握Kotlin语法糖,可以帮助我们简化代码,节省开发时间,提高效率。一般配合Google提供的KTX库即可完成大部分的项目开发,熟练掌握扩展函数和高阶函数的使用更是能为代码简化插上翅膀。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 集合创建 像创建一个数组一样初始化一个含有默认值的集合。避免了先创建,再赋值,这一点在java中是做不到的 普通集...
    YC_JS阅读 1,875评论 1 2
  • Kotlin笔记 要理解Java与Kotlin的区别,就要从最根本的上来理解。Java是解释型语言(边解释成二进制...
    FFFSnow阅读 984评论 0 0
  • 前言 从谨慎地在项目中引入kotlin到全部转为kotlin开发我们用了大概半年的时间。这中间经历了从在一个小功能...
    申国骏阅读 4,985评论 9 40
  • Kotlin 函数: 示例: params: String 函数参数 fun function(params: ...
    奔跑吧李博阅读 3,308评论 0 13
  • 一、领域特定语言 DSL的概念 DSL(domain specific language),即领域专用语言:专门解...
    大鹏的鹏阅读 759评论 0 0