关于 kotlin 扩展函数的一些知识:
- 从 java 的角度来看,kotlin 扩展函数是静态方法,它们将它们扩展的类作为参数
这使得它们很难与常规的旧静态函数区分开来。最初我什至不确定是否有区别。
所以让我们弄清楚是否有区别。
ExtensionFunctions.kt 中的声明:
class Test
fun bar(test : Test){}
fun Test.bar2(){}
fun Test.foo45(bar : Test, i :Int): Int = i
一些命令行:
francis@debian:~/test76/target/classes/io/github/pirocks$ javap -p -c -s -l ExtensionFunctionsKt.class
Compiled from "ExtensionFunctions.kt"
public final class io.github.pirocks.ExtensionFunctionsKt {
public static final void bar(io.github.pirocks.Test);
descriptor: (Lio/github/pirocks/Test;)V
Code:
0: aload_0
1: ldc #9 // String test
3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
6: return
LineNumberTable:
line 6: 6
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 test Lio/github/pirocks/Test;
public static final void bar2(io.github.pirocks.Test);
descriptor: (Lio/github/pirocks/Test;)V
Code:
0: aload_0
1: ldc #19 // String $this$bar2
3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
6: return
LineNumberTable:
line 8: 6
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 $this$bar2 Lio/github/pirocks/Test;
public static final int foo45(io.github.pirocks.Test, io.github.pirocks.Test, int);
descriptor: (Lio/github/pirocks/Test;Lio/github/pirocks/Test;I)I
Code:
0: aload_0
1: ldc #23 // String $this$foo45
3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
6: aload_1
7: ldc #24 // String bar
9: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
12: iload_2
13: ireturn
LineNumberTable:
line 10: 12
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 $this$foo45 Lio/github/pirocks/Test;
0 14 1 bar Lio/github/pirocks/Test;
0 14 2 i I
<further output omitted>
francis@debian:~/test76/target/classes/io/github/pirocks$
如您所见,除了第一个参数名称外,常规静态函数和扩展函数之间没有太大区别。扩展函数参数被命名为$this$functionName
. 我们可以通过解析字节码并检查参数名称来使用它来确定函数是否具有扩展变量。值得一提的是,这有点 hacky,如果有问题的类已经通过字节码混淆器运行,则可能无法工作。因为自己编写字节码解析器需要做很多工作,所以我使用 commons-bcel 为我完成所有工作。
扩展功能.kt:
package io.github.pirocks
import org.apache.bcel.classfile.ClassParser
class Test
fun bar(test : Test){}
fun Test.bar2(){}
fun Test.foo45(bar : Test, i :Int): Int = i
fun main(args: Array<String>) {
val classFileInQuestionStream = "Just wanted an object instance".javaClass.getResourceAsStream("/io/github/pirocks/ExtensionFunctionsKt.class")!!
val parsedClass = ClassParser(classFileInQuestionStream, "ExtensionFunctionsKt.class").parse()
parsedClass.methods.forEach { method ->
if(method.localVariableTable.localVariableTable.any {
it.name == ("\$this$${method.name}")
}){
println("Is an extension function:")
println(method)
}
}
}
以上应输出:
Is an extension function:
public static final void bar2(io.github.pirocks.Test $this$bar2) [RuntimeInvisibleParameterAnnotations]
Is an extension function:
public static final int foo45(io.github.pirocks.Test $this$foo45, io.github.pirocks.Test bar, int i) [RuntimeInvisibleParameterAnnotations]
Commons-bcel 还可以为您提供每个扩展功能的类型/名称/属性信息。
您在问题中提到使用 Int 上的扩展函数执行此操作。这更棘手,因为absoluteValue
被声明了,谁知道在哪里(Intellij Ctrl+B 告诉我它位于这个名为 MathH.kt 的巨大文件中,它实际上是 MathKt.class,在包 kotlin.math 中,包含在一些随机 jar 中来自行家)。由于不是每个人都会从 maven 获得相同的随机 jar,因此最好的做法是在System.getProperty("java.class.path")
. AnnoyinglyabsoluteValue
被声明为内联函数,因此在 stdlib jar 中没有它的踪迹。这不适用于所有 kotlin 标准库扩展函数。因此,您可以使用以下内容获取 stdlib 中的所有扩展函数(更正:有两个 stdlib jar,因此仅获取在 中声明的扩展函数kotlin-stdlib-version-number
)。
package io.github.pirocks
import org.apache.bcel.classfile.ClassParser
import java.nio.file.Paths
import java.util.jar.JarFile
class Test
fun bar(test: Test) {}
fun Test.bar2() {}
fun Test.foo45(bar: Test, i: Int): Int = i
fun main(args: Array<String>) {
val jarPath = System.getProperty("java.class.path").split(":").filter {
it.contains(Regex("kotlin-stdlib-[0-9]\\.[0-9]+\\.[0-9]+\\.jar"))
}.map {
Paths.get(it)
}.single()//if theres more than one kotlin-stdlib we're in trouble
val theJar = JarFile(jarPath.toFile())
val jarEntries = theJar.entries()
while (jarEntries.hasMoreElements()) {
val entry = jarEntries.nextElement()
if (entry.name.endsWith(".class")) {
val cp = ClassParser(theJar.getInputStream(entry), entry.getName())
val javaClass = cp.parse()
javaClass.methods.forEach { method ->
if (method.localVariableTable?.localVariableTable?.any {
it.name == ("\$this$${method.name}")
} == true) {
println("Is an extension function:")
println(method)
}
}
}
}
}
编辑:
至于实际回答有关如何在包中获取扩展功能的问题:
您需要遍历类路径中的每个条目,包括类和 jar,并检查与所需包匹配的任何类。至于确定一个类的包,你可以使用 commons-bcel 函数JavaClass::getPackageName
。