Kotlin语法糖总结
一、前言
使用kotlin开发项目已经有一段时间,在使用kotlin的过程中,发现了许多很方便的语法糖,可以有效简洁代码。在这里做个总结记录,方便后续查阅。
二、kotlin基础语法常识
1、 新建对象不需要new
关键字。语句也不需要;
结尾,但加上;
也不会报错。
//java
StringBuffer buffer = new StringBuffer();
//kotlin
var buffer = StringBuffer()
2、var
是kotlin保留字,用于声明变量。与之相对的是val
,表明引用不可变,但可以改变对象的值,使用var
或val
声明变量后,kotlin会根据上下文进行类型推断。
var name = ""
var age = 18
3、kotlin 用 :
取代了implements
和extends
保留字。
//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、所有定义了setter
和getter
方法的字段,在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 扩展程序可以为 Jetpack
、Android
平台及其他 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库即可完成大部分的项目开发,熟练掌握扩展函数和高阶函数的使用更是能为代码简化插上翅膀。