KotlinInline关键字

Kotlin 中的内联函数(Inline Function)是一种特殊的函数,与普通函数不同, 编译器会在编译时将内联函数中的代码插入到调用处, 避免函数调用的开销。它的作用和 C++ 中的内联函数类似,都是为了提高代码的执行效率。

定义内联函数

内联函数 的定义和普通函数类似, 只是在函数名前添加 inline 关键字即可。

kotlin 复制代码
inline fun inlineFunction(block: () -> Unit) {
    println("Inline function")
    block()
}

从语法上看,调用方式和普通函数相似

kotlin 复制代码
fun main() {
    inlineFunction {
        println("Hello, World!")
    }
}

以上代码与下面的代码在执行效果上完全一致

kotlin 复制代码
fun main() {
    println("Inline function")
    println("Hello, World!")
}

内联函数的return

kotlinlambda 表达式return必须使用 return@scope 来指定返回的作用域,由于内联函数的代码会被插入到调用处,所以内联函数可以使用 return 而不必须添加@限定符。比如

kotlin 复制代码
fun main() {
    inlineFunction {
        println("Hello, World!")
        return
    }
}

而普通函数则必须使用 return@scope 来返回, 比如

kotlin 复制代码
fun main() {
    normalFunction {
        println("Hello, World!")
        return@normalFunction
    }
}

为什么?因为内联函数的代码会被插入到调用处,所以上面的内联函数返回就相当于

kotlin 复制代码
fun main() {
    println("Inline function")
    println("Hello, World!")
    // 这里的return是可以不添加限定符直接返回的
    return
}

Kotlin 中的 return

从上面对内联函数的描述中,我们可以思考一个问题:什么时候return不需要添加@限定符呢?

  1. 当内联函数的lambda参数在顶层函数中被调用时,此时不添加限定符,return 就相当于直接返回顶层函数。
  2. 匿名函数中的return, 也不需要添加@限定符

内联函数在非内联函数中调用的时候, 就需要使用@限定符来指定返回的作用域。比如

kotlin 复制代码
fun main() {
    normalFunction {
        inlineFunction {
            println("Hello, World!")
            // 表示返回到inlineFunction
            return@inlineFunction
            
            // 或者返回到normalFunction
            // return@normalFunction
        }
    }
    inlineFunction {
        println("Hello, World!")
        // 直接返回到顶层函数,不需要添加@限定符
        return
        
        // 或者返回到normalFunction
        // return@normalFunction
    }

    inlineFunction {
        inlineFunction {
            // 直接返回到顶层函数,可以不添加@限定符
            return
            
            // 或者返回到inlineFunction
            // return@inlineFunction
        }
    }

    normalFunction(fun ():Unit {
        // 匿名函数中的return, 不需要添加@限定符
        return
    })
}

但是这里有一个问题,加了@限定符的return和不加@限定符的return有什么区别呢?

  • 如果加了限定符,退出的只是当前作用域
  • 而不加限定符,退出的是整个函数

Kotlin 中的 return 设计

那么,为什么Kotlin要设计成这样呢?为什么不设计的和Java一样,return返回的只是当前的作用域呢?目的有两个

  1. 增加比Java代码更大的灵活性
  2. 避免使用内联函数时的逻辑混淆
1. 增加比Java代码更大的灵活性

Java代码中,lambda表达式中的return只能返回当前lambda作用域,而Kotlin中的return可以返回任意作用域。这就增加了比Java代码更大的灵活性。

比如我们有一个需求,遍历一个列表,如果符合条件,终止遍历,我们看看在Java中怎么实现

java 复制代码
public <T> void breakOnCondition(List<T> list, Predicate<T> predicate) {
    for (T t : list) {
        if (predicate.test(t)) {
            return;
        }
    }
}

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    breakOnCondition(list, i -> i == 3);
}

那么,在Kotlin中我们甚至不需要这样设计。对于遍历场景,我们可以直接使用lambda表达式结合 return@scope 来终止当前遍历操作。

kotlin 复制代码
fun main() {
    val list = listOf(1, 2, 3, 4, 5)
    list.forEach {
        if (it == 3) {
            // 直接返回forEach循环
            return@forEach
        }
    }
}
2. 避免使用内联函数时的逻辑混淆

如果可以不带有@限定符,那么用户在编写代码的时候,很难确定程序的行为,比如下面的代码

kotlin 复制代码
fun normalFunction(block: () -> Unit) = block()
inline fun inlineFunction(block: () -> Unit) = block()

fun main() {
    normalFunction {
        // 这里是无法编译通过的,我们假定可以通过
        return
    }
    // 会被执行
    println("After normalFunction")
    inlineFunction {
        return
    }
    // 不会被执行
    println("After inlineFunction")
}

假设上面的代码正确,并且可以编译通过。

这里的 normalFunction 中的return,结束的是 normalFunction 函数。

而 inlineFunction 中的return,结束的却是main函数,因为inlineFunction 中的代码会被插入到调用处,所以inlineFunction 中的return 就相当于直接返回main函数。

那么用户在编写代码的时候,就很容易混淆。

而如果非内联函数的lambda参数要求只能使用return@scope来返回,就会避免这种容易混淆的情况。

kotlin 复制代码
fun main() {
    normalFunction {
        return@normalFunction
    }
    // 会被执行
    println("After normalFunction")

    inlineFunction {
        return@inlineFunction
    }
    // 会被执行
    println("After inlineFunction")

    inlineFunction {
        return
    }
    // 不会被执行
    println("After inlineFunction")
}

reified 类型参数

reified 类型参数是Kotlin中一种特殊的类型参数,它可以在运行时获取到类型信息。还记得Java中怎么获取泛型类型吗?

java 复制代码
public <T> T parseObj(String json, Class<T> clazz) {
    return new Gson().fromJson(json, clazz);
}

在Java中,我们必须添加 Class<T> clazz 参数来指定泛型类型。否则,我们无法在运行时获取到泛型类型。因为Java代码中如果没有和泛型相关的对象传入进来的话,是无法在运行时获取到泛型类型的。

但是在Kotlin的内联函数中,提供了reified 类型参数,它可以在函数不提供参数信息情况下,获取到泛型类型。比如下面的代码

kotlin 复制代码
inline fun <reified T> parseObj(json: String): T {
    return new Gson().fromJson(json, T::class.java)
}

想象一下这个参数可以给我们带来多大的便利性,我们可以直接在函数中使用T::class.java来获取到泛型类型,而不需要传入Class<T> clazz 参数。
MybatisPlus 提供了 KtQueryChainWrapper 类,通过这个类,可以通过lambda的方式构建查询参数。

kotlin 复制代码
@Autowired
private lateinit var baseMapper: BaseMapper<Config>

val chain = KtQueryChainWrapper(baseMapper, entityClass=Config::class.java)
val config = chain.eq(Config::name, "test").one()

为了方便,我们可以为 BaseMapper 添加一个扩展方法,来获取 KtQueryChainWrapper 类。

kotlin 复制代码
fun <T : Any> BaseMapper<T>.query(): KtQueryChainWrapper<T> {
    // 很要命的是 mybatisplus 要求必须填写 entityClass 参数,否则会报错
    return KtQueryChainWrapper(baseMapper = this, entityClass = entityClass())  
}

// 使用的时候就会很方便
val config = baseMapper.query().eq(Config::name, "test").one()

但是,怎么获取 entityClass 呢?如果没有 reified 关键字, 我们只能通过反射来获取。比如下面的代码

kotlin 复制代码
private fun <T : Any> BaseMapper<T>.entityClass(): Class<T> {  
    val searchEntityClass = javaClass.searchEntityClass<T>()  
    if (searchEntityClass != null) {  
        return searchEntityClass  
    }  
    throw IllegalArgumentException("无法获取实体类")  
}
private fun <T> Class<*>.searchEntityClass(): Class<T>? {
    for (type in genericInterfaces) {
        if (type is ParameterizedType) {
            if (type.rawType == BaseMapper::class.java) {
                return type.actualTypeArguments[0] as Class<T>
            }
        } else {
            if (type is Class<*>) {
                return type.searchEntityClass()
            }
        }
    }
    return null

}

这时候如果我们使用 内联函数 + reified 类型参数,就可以避免使用反射来获取泛型类型。比如下面的代码

kotlin 复制代码
inline fun <reified T : Any> BaseMapper<T>.query(): KtQueryChainWrapper<T> {
    // 在使用了reified 类型参数后,就可以直接使用 T::class.java 来获取泛型类型
    return KtQueryChainWrapper(baseMapper = this, entityClass = T::class.java)  
}

可以看到,使用了 reified 类型参数后,就可以直接使用 T::class.java 来获取泛型类型,而不需要通过反射来获取。

相关推荐
用户69371750013845 小时前
27.Kotlin 空安全:安全转换 (as?) 与非空断言 (!!)
android·后端·kotlin
用户985120035835 小时前
KotlinInline关键字
kotlin
Kapaseker7 小时前
如果我问你 Context,你扛得住吗?
android·kotlin
RainyJiang16 小时前
聊聊协程里的 Semaphore:别让协程挤爆门口
android·kotlin
spencer_tseng1 天前
eclipse 4.7 kotlin android
android·kotlin
Kapaseker1 天前
十分钟速览 Kotlin Flow 操作符
android·kotlin
用户69371750013841 天前
25.Kotlin 空安全:Kotlin 的灵魂:可空性 (?) 与空安全
android·后端·kotlin
用户69371750013841 天前
26.Kotlin 空安全:安全调用:安全调用运算符 (?.) 与 Elvis 运算符 (?:)
android·后端·kotlin
モンキー・D・小菜鸡儿2 天前
Android14 新特性与适配指南
android·kotlin·安卓新特性