Kotlin 中的静态方法和伴随对象
与Java不同,Kotlin 不支持类的静态方法。大多数读者都知道静态方法不属于对象实例,而是属于类型本身。在 Kotlin 中,建议在包级别定义方法以实现静态方法的功能。让我们定义一个新的 Kotlin 文件并将其命名为 Static。在这个文件中,我们将放置一个函数的代码,该函数将返回输入字符串的第一个字符(如果输入为空,则会引发异常),如下所示:
Kotlin
fun showFirstCharacter(input:String):Char{
if(input.isEmpty()) throw IllegalArgumentException()
return input.first()
}
Kotlin
object Singleton{
private var count = 0
fun doSomething():Unit {
println("Calling a doSomething (${++count} call/-s in total)")
}
}
Kotlin
open class SingletonParent(var x:Int){
fun something():Unit{
println("X=$x")
}
}
object SingletonDerive:SingletonParent(10){}
Kotlin
interface StudentFactory {
fun create(name: String): Student
}
class Student private constructor(val name: String) {
companion object : StudentFactory {
override fun create(name: String): Student {
return Student(name)
}
}
}
然后,在您的代码中,您可以简单地调用showFirstCharacter (“Kotlin 很酷!”)。编译器在这里为您完成一些工作。使用javap,我们可以看一下生成的字节码。只需运行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
}
从打印输出中可以看出,编译器实际上已经为我们生成了一个类,并将其标记为final;如您所知,它不能被继承。在这个类中,编译器添加了我们定义的函数。让我们从程序入口点调用这个方法,然后再次使用实用程序 javap 我们可以查看字节码的样子:
fun main(args: Array) {
println("First lettter:" + 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/gfg/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
}
我们省略了为doSomething方法生成的代码,因为它不是本主题的重点。编译器再次创建了一个类并将其标记为final。此外,它还引入了一个名为 INSTANCE 的成员并将其标记为静态。有趣的部分是在列表的末尾,您可以在其中看到 static{};入口。这是类初始化器,它只被调用一次,JVM 将确保这发生在之前:
- 类的一个实例被创建
- 调用类的静态方法
- 分配了类的静态字段
- 使用了非常量的静态字段
- 为顶级类执行词法嵌套在类中的断言语句
在这种情况下,代码在第一次调用doSomething之前被调用,因为我们访问了静态成员INSTANCE(参见下面的 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
您可以看到,在这两种情况下,doSomething 都被称为虚拟方法。原因是您可以创建一个从给定类继承的单例,如下面的示例所示:
科特林
open class SingletonParent(var x:Int){
fun something():Unit{
println("X=$x")
}
}
object SingletonDerive:SingletonParent(10){}
有一种方法可以像在Java中那样调用静态方法。为此,您必须将对象放在一个类中并将其标记为伴生对象。至少对 Scala 有入门级知识的人会熟悉伴生对象的概念。以下示例使用工厂设计模式来构造 Student 的实例:
科特林
interface StudentFactory {
fun create(name: String): Student
}
class Student private constructor(val name: String) {
companion object : StudentFactory {
override fun create(name: String): Student {
return Student(name)
}
}
}
如您所见,Student 类型的构造函数已被标记为私有。因此,除了 Student 类或伴生对象之外,不能从任何地方调用它。同伴类对 Student 的所有方法和成员具有完全的可见性。从代码中,您需要调用Student.create (“Jack Wallace”)来创建一个新的 Student 实例。如果查看构建输出,您会注意到为 Student 生成了两个类:一个是Student.class ,另一个是Student$Companion.class 。让我们看看对Student.create的调用是如何转换为字节码的:
public final class com.gfg.kotlin.gfgkt {
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/Student.Companion:Lcom/gfg/kotlin/Student$Companion;
9: ldc #23 //String Jack Wallace
11: invokevirtual #29 //Method
com/gfg/kotlin/Student$Companion.create:(Ljava/lang/String;)Lcom/gfg/kotlin/Student;
14: pop
15: return
}
在第 6 行,您会注意到调用了静态成员getstatic 。正如您可能想象的那样, Student.Companion类型的 Student 类中添加了一个静态字段:
public final class com.gfg.kotlin.Student {
public static final com.gfg.kotlin.Student$CompanionCompanion;
public final java.lang.String getName();
static {};
Code:
0: new #39 //class
com/gfg/kotlin/Student$Companion
3: dup
4: aconst_null
5: invokespecial #42 //Method
com/gfg/kotlin/Student$Companion."":(Lkotl
in/jvm/internal/DefaultConstructorMarker;)V
8: putstatic #44 //Field
Companion:Lcom/gfg/kotlin/Student$Companion;
11: return
public
com.gfg.kotlin.Student(java.lang.String,kotlin.jvm.internal.DefaultConstructorMarker);
Code:
0: aload_0
1: aload_1
2: invokespecial #24 //Method "":(Ljava/lang/String;)V
5: return
这个代码片段证明了这个假设是正确的。您可以看到 Companion成员被添加到我们的课程中。再一次,该类获取生成的类初始化程序代码以创建我们的伴随类的实例。 Student.create是编写诸如Student.Companion.create()之类的代码的简写。如果您尝试创建Student.Companion的实例(即 val c = Student.Companion ),则会收到编译错误。伴随对象遵循所有继承规则。