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  void breakOnCondition(List list, Predicate predicate) {
    for (T t : list) {
        if (predicate.test(t)) {
            return;
        }
    }
}

public static void main(String[] args) {
    List 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 parseObj(String json, Class clazz) {
    return new Gson().fromJson(json, clazz);
}

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

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

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

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

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

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

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

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

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

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

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

}

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

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

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

相关推荐
FunnySaltyFish7 小时前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
Kapaseker13 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z3 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton4 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream4 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam4 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker5 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin
糖猫猫cc5 天前
Kite:两种方式实现动态表名
java·kotlin·orm·kite
如此风景5 天前
kotlin协程学习小计
android·kotlin