📜  Kotlin 中的静态方法和伴随对象

📅  最后修改于: 2022-05-13 01:55:49.496000             🧑  作者: Mango

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 ),则会收到编译错误。伴随对象遵循所有继承规则。