成员引用
您可以将成员引用作为函数参数传递。
对于函数、属性和构造函数,成员引用 可以替换只是调用相应函数、属性或构造函数的平凡 Lambda。
成员引用使用双冒号将类名与函数或属性分隔开。在这里,Message::isRead
是一个成员引用:
// MemberReferences/PropertyReference.kt
package memberreferences1
import atomictest.eq
data class Message(
val sender: String,
val text: String,
val isRead: Boolean
)
fun main() {
val messages = listOf(
Message("Kitty", "Hey!", true),
Message("Kitty", "Where are you?", false))
val unread =
messages.filterNot(Message::isRead)
unread.size eq 1
unread.single().text eq "Where are you?"
}
要筛选未读消息,我们使用库函数 filterNot()
,该函数接受谓词。在我们的情况下,谓词指示消息是否已读。我们可以传递一个 Lambda,但我们传递属性引用 Message::isRead
。
当指定非平凡的排序顺序时,属性引用非常有用:
// MemberReferences/SortWith.kt
import memberreferences1.Message
import atomictest.eq
fun main() {
val messages = listOf(
Message("Kitty", "Hey!", true),
Message("Kitty", "Where are you?", false),
Message("Boss", "Meeting today", false))
messages.sortedWith(compareBy(
Message::isRead, Message::sender)) eq
listOf(
// 首先是未读消息,按发送者排序:
Message("Boss", "Meeting today", false),
Message("Kitty",
"Where are you?", false),
// 然后是已读消息,也按发送者排序:
Message("Kitty", "Hey!", true))
}
库函数 sortedWith()
使用比较器对列表进行排序,比较器是用于比较两个元素的对象。库函数 compareBy()
基于其参数构建比较器,参数是一系列的谓词。使用具有单个参数的 compareBy()
等效于调用 sortedBy()
。
函数引用
假设您想要检查 List
是否包含任何重要消息,而不仅仅是未读消息。您可能有许多复杂的标准来决定“重要”是什么意思。您可以将此逻辑放入 Lambda 中,但该 Lambda 可能很容易变得又大又复杂。如果您将其提取到单独的函数中,代码会更容易理解。在 Kotlin 中,您不能将函数传递到函数类型的位置,但可以传递对该函数的引用:
// MemberReferences/FunctionReference.kt
package memberreferences2
import atomictest.eq
data class Message(
val sender: String,
val text: String,
val isRead: Boolean,
val attachments: List<Attachment>
)
data class Attachment(
val type: String,
val name: String
)
fun Message.isImportant(): Boolean =
text.contains("Salary increase") ||
attachments.any {
it.type == "image" &&
it.name.contains("cat")
}
fun main() {
val messages = listOf(Message(
"Boss", "Let's discuss goals " +
"for next year", false,
listOf(Attachment("image", "cute cats"))))
messages.any(Message::isImportant) eq true
}
这个新的 Message
类添加了一个 attachments
属性,扩展函数 Message.isImportant()
使用了这个信息。在调用 messages.any()
时,我们创建了对扩展函数的引用 - 引用不限于成员函数。
如果有一个以 Message
作为唯一参数的顶级函数,您可以将其作为引用传递。当您创建对顶级函数的引用时,没有类名,因此写为 ::function
:
// MemberReferences/TopLevelFunctionRef.kt
package memberreferences2
import atomictest.eq
fun ignore(message: Message) =
!message.isImportant() &&
message.sender in setOf("Boss", "Mom")
fun main() {
val text = "Let's discuss goals " +
"for the next year"
val msgs = listOf(
Message("Boss", text, false, listOf()),
Message("Boss", text, false, listOf(
Attachment("image", "cute cats"))))
msgs.filter(::ignore).size eq 1
msgs.filterNot(::ignore).size eq 1
}
构造函数引用
您可以使用类名创建对构造函数的引用。
在这里,names.mapIndexed()
使用构造函数引用 ::Student
:
// MemberReferences/ConstructorReference.kt
package memberreferences3
import atomictest.eq
data class Student(
val id: Int,
val name: String
)
fun main() {
val names = listOf("Alice", "Bob")
val students =
names.mapIndexed { index, name ->
Student(index, name)
}
students eq listOf(Student(0, "Alice"),
Student(1, "Bob"))
names.mapIndexed(::Student) eq students
}
mapIndexed()
在 Lambdas 中已介绍。它将 names
中的每个元素转换为该元素的索引以及元素本身。在 students
的定义中,这些被显式地映射到构造函数,但是使用 names.mapIndexed(::Student)
可以实现相同的效果。因此,函数和构造函数引用可以消除只是传递到 Lambda 中的一长串参数的需要。函数和构造函数引用通常比 Lambda 更具可读性。
扩展函数引用
要生成对扩展函数的引用,将引用前缀添加到扩展类型的名称:
// MemberReferences/ExtensionReference.kt
package memberreferences
import atomictest.eq
fun Int.times47() = times(47)
class Frog
fun Frog.speak() = "Ribbit!"
fun goInt(n: Int, g: (Int) -> Int) = g(n)
fun goFrog(frog:
Frog, g: (Frog) -> String) =
g(frog)
fun main() {
goInt(12, Int::times47) eq 564
goFrog(Frog(), Frog::speak) eq "Ribbit!"
}
在 goInt()
中,g
是一个期望 Int
参数并产生 Int
的函数。在 goFrog()
中,g
期望一个 Frog
并产生一个 String
。
练习和解答可在 www.AtomicKotlin.com 找到。