随着Kotlin不断升级,一些Kotlin语法设计被引入到该语言中。我写这篇文章是提醒我们它们的特性和重要性。
1. fun interface (SAM):函数式(SAM)接口
fun interface (SAM)
:函数式(SAM)接口 只有一个抽象方法的接口称为函数式接口或 SAM(单一抽象方法)接口。函数式接口可以有多个非抽象成员,但只能有一个抽象成员。
许多语言(如Java)最初并没有将函数作为别的函数的参数、函数的返回值,赋值给变量或存储在数据结构中。这里将通过使用带有一个抽象方法(又名单一抽象方法或SAM)接口的接口来模拟这种功能。
fun interface OnClickListener {
// 只有一个抽象方法
fun onClick(view: View)
}
fun Button.setOnClickListenerXXX(listener: OnClickListener) {
// 监听器在某个时间点被调用
listener.onClick(this)
}
当像上面那样使用fun interface
时,我们通常必须传递一个实现此SAM接口的对象实例。
val clickListener = object: OnClickListener {
override fun onClick(view: Button) {
Toast.makeText(context, "Button 1 Clicked", Toast.LENGTH_SHORT).show()
}
}
//你现在可以传递这个函数
myButton.setOnClickListenerXXX(clickListener)
通常,jvm通过lambdas可以简化一个更简洁的版本来实现上面的代码:
myButton.setOnClickListenerXXX { view ->
Toast.makeText(view.context, "testing 1", Toast.LENGTH_SHORT).show()
}
参考官方文档:https://kotlinlang.org/docs/fun-interfaces.html
1.1、fun interface(函数式接口) 「vs」 function types(函数类型)
我们知道Kotlin将“函数类型”的概念直接引入语言
函数类型和函数式接口不是一回事,但它们的目的相似即使将函数作为别的函数的参数、函数的返回值,赋值给变量或存储在数据结构中。
在大多情况下我们都是使用函数类型。这也挺让人疑惑(存在两个相似的概念),因为它们在用法上看起来非常相似。让我们来比较一下:
// -------------------------
// 定义函数式接口
// -------------------------
fun interface OnClickListener {
fun onClick(view: View)
}
fun Button.setOnClickListenerXXX(listener: OnClickListener) {
listener.onClick(this)
}
// -------------------------
// 定义函数类型
// -------------------------
fun Button.setOnClickListenerYYY(listener: (View) -> Unit) {
listener.invoke(this)
}
代码段(View) -> Unit
是Kotlin可以将参数定义为函数类型。
在实际使用中,您不会发现上面两个代码段的使用方式有什么不同
myButton.setOnClickListenerXXX { view ->
// 对按钮触发点击监听逻辑
Toast.makeText(view.context, "testing 1", Toast.LENGTH_SHORT).show()
}
myButton.setOnClickListenerYYY { view ->
// 对按钮触发点击监听逻辑
Toast.makeText(view.context, "testing 2", Toast.LENGTH_SHORT).show()
}
我个人建议是:尽可能使用函数类型。
2. type alias:类型别名
type alias
:类型别名 类型别名为现有类型提供替代名称。 如果类型名称太长,你可以另外引入较短的名称,并使用新的名称替代原类型名。
它概念很简单,在与较长的函数类型定义一起使用时特别有用。
typealias MyHandler = (Int, String) -> Unit
// 现在只需使用MyHandler作为类型定义替代这种很长的函数类型 (Int, String) -> Unit
val myHandler: MyHandler = { intValue, stringValue ->
// ...
}
3. import alias:导入别名
类型别名的有趣之处在于,您不局限于仅对函数类型使用它。你可以用它来“重新定义”任何类型(甚至缩短尴尬的长类名):
typealias DropdownView = Spinner
typealias AVD = AnimatedVectorDrawable
但这不是最佳方案。Kotlin有一个更好的方法来做到这一点,称为“导入别名”:
import android.widget.Spinner as DropdownView
import android.graphics.drawable.AnimatedVectorDrawable as AVD
private lateinit var myDropDown: DropdownView
private lateinit var myAVD: AVD
导入别名的另一个重要用途是可以消除具有相同名称的多个类型之间的歧义。假设你有一个用泛型名定义的类型,但你也有一个工具库自动生成一个具有完全相同名称的不同类。通过使用import alias 1消除歧义,可以在同一个类中使用这两种类型:
import java.sql.Date as SqlDate
import java.util.Date as JavaDate
val date1: SqlDate = SqlDate(0)
val date2: JavaDate = JavaDate()
4. value class:值类
value class
:值类
这里将解释为什么我们需要值类,以及为什么一些情况下我们不应该使用typealias
(类型别名)或data calss
(数据类)。
场景如下:
fun auth1(userId: Int, pin: Int): Boolean
val u: Int = 909
val p: Int = 1234
auth1(u, p)
auth1(p, u) //没有编译错误
看到问题了吗?userid和pin都是Int类型,但用途不同。如果编译器告诉我们传递了错误的字段,那不是很好吗? 你可能会想到这可以用data class
来解决这个问题:
data class UserId(val field: Int)
data class Pincode(val field: Int)
fun auth2(userId: UserId, pin: Pincode): Boolean
val u = UserId(909)
val p = Pincode(1234)
auth2(u, p)
auth2(p, u) // 🛑 编译错误 (✅)
这种方法“有效”,但有一个大问题:data class内存分配非常昂贵。使用Int时内存分配在栈,而使用data class对象时,内存分配入堆。
我们可以使用typealias
(类型别名)来避免分配成本。但是我们遇到了最初问题,即没有编译错误:
typealias UserId = Int
typealias Pincode = Int
fun auth3(userId: Int, pin: Int): Boolean
val u = UserId(909)
val p = Pincode(1234)
auth3(u3, p3)
auth3(p3, u3) // ️没有编译错误
为了解决这个问题,Kotlin在1.5版本中引入了value class
“值类”的概念:
@JvmInline
value class UserId(val field: Int)
@JvmInline
value class Pincode(val field: Int)
fun auth4(userId: UserId, pin: Pincode): Boolean
val u = UserId(909)
val p = Pincode(1234)
auth4(u, p)
auth4(p, u) // 🛑 编译错误 (✅)
这很像data class,但不同之处在于,在底层,没有分配成本。那么为什么不全部使用value calss
(值类)而还要有data class
数据类呢?value calss
(值类)只能接受一个基本字段,所以data class
是有它的使用范围的。
-
value class在Kotlin 1.5中引入 -
value class以前为inner class“内联类”,已废弃
5. data object:数据对象
data object
:数据对象
您是否也尝试在调试期间打印单例对象?
object Student
println("该学生是 ${Student}")
// 该学生是 Student@37f8bb67
Kotlin引入了data object
“数据对象”的概念。可以把它看作是在你的单例中添加了一个更好的. tostring()。
data object Student
println("该学生是 ${Student}")
// 该学生是 Student
发表评论