1. Функции области
Scope Functions :The Kotlin standard library contains several functions whose sole purpose is to execute a block of code within the context of an object. When you call such a function on an object with a lambda expression provided, it forms a temporary scope. In this scope, you can access the object without its name.
Функция с ограниченной областью: это функция стандартной библиотеки Kotlin, единственной целью которой является выполнение блока кода в контексте объекта. Когда вы вызываете такую функцию для объекта с лямбда-выражением, она формирует временную область. В пределах этой области вы можете получить доступ к объекту, не используя его имя.
Функции области действия Kotlin включают в себя: позволить, запустить, с, применить, также и т. д. В этой статье основное внимание уделяется наиболее часто используемым командам let, run, apply и их корректному использованию.
1.1 Использование функции применения
Функция apply означает, что с помощью this в функциональном блоке можно сослаться на объект, а возвращаемым значением является сам объект. В связанных вызовах мы можем использовать его, не нарушая цепочку.
/**
* Calls the specified function [block] with `this` value as its receiver and returns `this` value.
*/
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
Например:
object Test {
@JvmStatic
fun main(args: Array<String>) {
val result ="Hello".apply {
println(this+" World")
this+" World" // apply 会返回该对象自己,所以 result 的值依然是“Hello”
}
println(result)
}
}
Результаты:
Hello World
Hello
Первая строка печатается в замыкании, вторая строка является результатом результата, который по-прежнему «Привет».
1.2 Использование функции запуска
Функция запуска аналогична функции применения, но функция запуска возвращает значение последней строки.
/**
* Calls the specified function [block] and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
Например:
object Test {
@JvmStatic
fun main(args: Array<String>) {
val result ="Hello".run {
println(this+" World")
this + " World" // run 返回的是最后一行的值
}
println(result)
}
}
Результаты:
Hello World
Hello World
Первая строка печатается в замыкании, а вторая строка является результатом результата, который возвращает значение последней строки в замыкании, поэтому «Hello World» также печатается.
1.3 Использование функции let
Функция let принимает текущий объект в качестве параметра it замыкания, а возвращаемое значение является последней строкой в функции или указывает return.
Это чем-то похоже на функцию запуска. Разница между функцией let и функцией запуска заключается в том, что функция let может ссылаться на объект через него внутри функции.
/**
* Calls the specified function [block] with `this` value as its argument and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
Обычно функция let используется вместе с ?:
obj?.let {
....
}
Код функционального блока let может выполняться, если obj не равен null, что позволяет избежать возникновения исключений нулевого указателя.
2. Как изящно использовать Scope Functions?
Новички в Kotlin часто пишут такой код:
fun test(){
name?.let { name ->
age?.let { age ->
doSth(name, age)
}
}
}
Такой код сам по себе в порядке. Однако, поскольку функции let слишком много вложены друг в друга, это становится менее читабельным и менее элегантным. В конце этой статьи будет дано изящное написание.
Следующее в сочетании с ситуацией, возникшей в работе, суммирует некоторые методы, чтобы мы могли лучше использовать функции области видимости.
2.1 Использование оператора Элвиса
Оператор Элвиса является сокращением для тернарного условного оператора.Для оператора вида x = foo() ?foo() : bar() оператор Элвиса может быть записан в форме x = foo() ?:bar().
С помощью оператора Elvis и оператора безопасного вызова в Kotlin реализована простая и понятная проверка null и операция null.
//根据client_id查询
request.deviceClientId?.run {
//根据clientId查询设备id
orgDeviceSettingsRepository.findByClientId(this)?:run{
throw IllegalArgumentException("wrong clientId")
}
}
В приведенном выше коде фактически использовался оператор Элвиса, поэтому вы можете не использовать функцию запуска и напрямую вызвать исключение.
//根据client_id查询
request.deviceClientId?.run {
//根据clientId查询设备id
orgDeviceSettingsRepository.findByClientId(this)?:throw IllegalArgumentException("wrong clientId")
}
2.2 Использование функций высшего порядка
Когда функция let используется в нескольких местах, сама читабельность невысока.
fun add(request: AppVersionRequestModel): AppVersion?{
val appVersion = AppVersion().Builder().mergeFrom(request)
val lastVersion = appVersionRepository.findFirstByAppTypeOrderByAppVersionNoDesc(request.appType);
lastVersion?.let {
appVersion.appVersionNo = lastVersion.appVersionNo!!.plus(1)
}?:let{
appVersion.appVersionNo = 1
}
return save(appVersion)
}
Затем напишите функцию более высокого порядка checkNull(), которая заменит использование двух функций let.
inline fun <T> checkNull(any: Any?, function: () -> T, default: () -> T): T = if (any!=null) function() else default()
Таким образом, приведенный выше код изменяется на этот:
fun add(request: AppVersionRequestModel): AppVersion?{
val appVersion = AppVersion().Builder().mergeFrom(request)
val lastVersion = appVersionRepository.findFirstByAppTypeOrderByAppVersionNoDesc(request.appType)
checkNull(lastVersion, {
appVersion.appVersionNo = lastVersion!!.appVersionNo.plus(1)
},{
appVersion.appVersionNo = 1
})
return save(appVersion)
}
2.3 Использование необязательного
При использовании JPA метод репозитория findById() сам возвращает необязательный объект.
fun update(requestModel: AppVersionRequestModel): AppVersion?{
appVersionRepository.findById(requestModel.id!!)?.let {
val appVersion = it.get()
appVersion.appVersion = requestModel.appVersion
appVersion.appType = requestModel.appType
appVersion.appUrl = requestModel.appUrl
appVersion.content = requestModel.content
return save(appVersion)
}
return null;
}
Следовательно, приведенный выше код может напрямую использовать функции Optional без функции let.
fun update(requestModel: AppVersionRequestModel): AppVersion?{
return appVersionRepository.findById(requestModel.id!!)
.map {
it.appVersion = requestModel.appVersion
it.appType = requestModel.appType
it.appUrl = requestModel.appUrl
it.content = requestModel.content
save(it)
}.getNullable()
}
Здесь getNullable() на самом деле является функцией расширения.
fun <T> Optional<T>.getNullable() : T? = orElse(null)
2.4 Использование связанных вызовов
Вложенность нескольких функций run, apply и let значительно снижает читабельность кода. Если вы не будете писать комментарии, то через долгое время забудете назначение этого кода.
/**
* 推送各种报告事件给商户
*/
fun pushEvent(appId:Long?, event:EraserEventResponse):Boolean{
appId?.run {
//根据appId查询app信息
orgAppRepository.findById(appId)
}?.apply {
val app = this.get()
this.isPresent().run {
event.appKey = app.appKey
//查询企业推送接口
orgSettingsRepository.findByOrgId(app.orgId)
}?.apply {
this.eventPushUrl?.let {
//签名之后发送事件
val bodyMap = JSON.toJSON(event) as MutableMap<String, Any>
bodyMap.put("sign",sign(bodyMap,this.accountSecret!!))
return sendEventByHttpPost(it,bodyMap)
}
}
}
return false
}
В приведенном выше коде просто есть вложенные отношения зависимости, мы можем попытаться превратить его в цепной вызов. После модификации улучшается читабельность и ремонтопригодность кода.
/**
* 推送各种报告事件给商户
*/
fun pushEvent(appId:Long?, event:EraserEventResponse):Boolean{
appId?.run {
//根据appId查询app信息
orgAppRepository.findById(appId).getNullable()
}?.run {
event.appKey = this.appKey
//查询企业信息设置
orgSettingsRepository.findByOrgId(this.orgId)
}?.run {
this.eventPushUrl?.let {
//签名之后发送事件
val bodyMap = JSON.toJSON(event) as MutableMap<String, Any>
bodyMap.put("sign",sign(bodyMap,this.accountSecret!!))
return sendEventByHttpPost(it,bodyMap)
}
}
return false
}
2.5 Применение
Понимая некоторые из вышеперечисленных методов, исходная функция test() просто нуждается в рефакторинге, чтобы определить функцию более высокого порядка notNull() .
inline fun <A, B, R> notNull(a: A?, b: B?,block: (A, B) -> R) {
if (a != null && b != null) {
block(a, b)
}
}
fun test() {
notNull(name, age) { name, age ->
doSth(name, age)
}
}
Функция notNull() может судить только о двух объектах.Если есть несколько объектов для оценки, как лучше с этим справиться? Ниже приведен один из способов.
inline fun <R> notNull(vararg args: Any?, block: () -> R) {
when {
args.filterNotNull().size == args.size -> block()
}
}
fun test() {
notNull(name, age) {
doSth(name, age)
}
}
3. Резюме
Kotlin сам по себе очень гибкий язык, использовать его для написания кода непросто, и он требует постоянного изучения и обобщения. Эта статья предназначена только для того, чтобы дать вам несколько идей, в надежде вдохновить вас.
Стек технологий Java и Android: еженедельно обновляйте и публикуйте оригинальные технические статьи, добро пожаловать, чтобы отсканировать QR-код общедоступной учетной записи ниже и подписаться, и с нетерпением ждем роста и развития вместе с вами.