与 Java 不同,Kotlin 不支持类的静态方法。大多数读者都知道,静态方法不属于对象实例,而是属于类型本身。在 Kotlin 中,建议我们在包级别定义方法,以实现静态方法的功能。让我们创建一个新的 Kotlin 文件并将其命名为 Static。在这个文件中,我们将编写一个函数的代码,该函数将返回输入字符串的第一个字符(如果输入为空,将抛出异常),代码如下:
fun showFirstCharacter(input:String):Char{
if(input.isEmpty()) throw IllegalArgumentException()
return input.first()
}
然后,在我们的代码中,我们可以直接调用 INLINECODEc5a25d50("Kotlin is cool!")。编译器在这里替我们完成了一些工作。使用 INLINECODE43681438,我们可以查看生成的字节码。只需运行 javap -c StaticKt.class 即可获得编译器生成的代码:
Compiled from "Static.kt"
public final class com.gfg.kotlin.StaticKt {
public static final char showFirstCharacter(java.lang.String);
Code:
0: aload_0
1: ldc #9 //String input
3: invokestatic #15 //Method
kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
...
40: aload_0
41: checkcast #17 //class java/lang/CharSequence
44: invokestatic #35 //Method
kotlin/text/StringsKt.first:(Ljava/lang/CharSequence;)C
47: ireturn
}
正如大家从输出中看到的,编译器实际上为我们生成了一个类,并将其标记为 INLINECODE94664ede;如大家所知,它是不可被继承的。在这个类中,编译器添加了我们定义的函数。让我们从程序入口点调用这个方法,再次使用 INLINECODE94a473fb 工具,我们可以看看字节码是什么样的:
fun main(args: Array) {
println("First letter:" + showFirstCharacter("Kotlin is cool")
}
Compiled from "gfg.kt"
public final class com.gfg.kotlin.gfgkt {
public static final void main(java.lang.String[]);
Code:
0: aload_0
...
18: ldc #29 //String Kotlin is cool
20: invokestatic #35 //Method
com/folder/kotlin.StaticKt.showFirstCharacter:(Ljava/lang/String;)C
}
为了简洁起见,大部分字节码被省略了,但在第 20 行大家可以看到对我们方法的调用;具体来说,这个调用是通过 invokestatic 例程进行的。
我们在讨论静态方法时,不能不提单例模式。单例是一种设计模式,它将给定类的实例化限制为一个实例。一旦创建,它将在程序的整个生命周期中存在。Kotlin 借鉴了 Scala 中的方法。以下是在 Kotlin 中定义单例的方法:
object Singleton{
private var count = 0
fun doSomething():Unit {
println("Calling a doSomething (${++count} call/-s in total)")
}
}
现在,我们可以从任何函数调用 Singleton.doSomething,每次都会看到计数器增加。如果你查看生成的字节码,你会发现编译器再次为我们完成了一些工作:
public final class com.gfg.kotlin.Singleton {
public static final com.gfg.kotlin.Singleton INSTANCE;
public final void doSomething();
Code:
0: new #10 // class java/lang/StringBuilder
43: return
...
static {};
Code:
0: new #2 //class
com/gfg/kotlin/Singleton
3: invokespecial #61 //Method "":()V
6: return
}
我们省略了为 INLINECODE3597438f 方法生成的代码,因为它不是本文的重点。编译器再次创建了一个类并将其标记为 INLINECODEe80d5918。此外,它引入了一个名为 INLINECODE6f930997 的成员并将其标记为 INLINECODE4584ac53。有趣的部分在于列表的末尾,大家可以看到 static {}; 条目。这是类初始化器,它只被调用一次,JVM 会确保这种情况发生在以下操作之前:
- 创建类的实例
- 调用类的静态方法
- 赋值类的静态字段
- 使用非常量静态字段
- 执行词法嵌套在类中的 top-level 类的断言语句
在这种情况下,代码在第一次调用 INLINECODE1b7b5027 之前被调用,因为我们访问了静态成员 INLINECODE34c64011(请参见下面的 getstatic 字节码例程)。如果我们调用这个方法两次,我们将得到以下字节码:
public static final void main(java.lang.String[]);
Code:
0: aload_0
1: ldc #9 // String args
3: invokestatic #15 //Method
kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
6: getstatic #21 //Field
com/gfg/kotlin/Singleton.INSTANCE:Lcom/gfg/kotlin/Singleton;
9: invokevirtual #25 //Method
com/gfg/kotlin/Singleton.doSomething:()V
12: getstatic #21 //Field
com/gfg/kotlin/Singleton.INSTANCE:Lcom/gfg/kotlin/Singleton;
15: invokevirtual #25 //Method
com/gfg/kotlin/Singleton.doSomething:()V
18: return
大家可以看到,在这两种情况下,INLINECODE5f2dfa9d 都是通过虚方法调用(INLINECODEb2e2772a)执行的,并且是针对同一个 INSTANCE 进行的。