Open In App

Static Methods and Companion Objects in Kotlin

Last Updated : 08 Jun, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

Unlike Java, Kotlin doesn’t support static methods for a class. Most readers will know that static methods do not belong to the object instance but rather to the type itself. In Kotlin, it is advisable to define methods at the package level to achieve the functionality of static methods. Let’s define a new Kotlin file and name it Static. Within this file, we will place the code for a function that will return the first character of the input string (if the input is empty, an exception will be raised), which is as follows:

Kotlin




fun showFirstCharacter(input:String):Char{
  if(input.isEmpty()) throw IllegalArgumentException()
  return input.first()
}


Then, in your code, you can simply call showFirstCharacter(“Kotlin is cool!”). The compiler is here to do some of the work for you. Using javap, we can take a look at the byte code generated. Just run javap -c StaticKt.class to get the code produced by the compiler:

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
}

As you can see from the printout, the compiler has actually generated a class for us and has marked it as final; it can’t be inherited, as you already know. Within this class, the compiler has added the function we defined. Let’s call this method from the program entry point and again using the utility javap we can look at what the bytecode looks like:

fun main(args: Array<String>) {
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/gfg/kotlin/StaticKt.showFirstCharacter:(Ljava/lang/String;)C
}

Most of the bytecode has been left out for the sake of simplicity, but at line 20 you can see there is a call to our method; in particular, the call is made via the invokestatic routine.

We can’t talk about static methods and not bring singletons into the discussion. A singleton is a design pattern that limits the instantiation of a given class to one instance. Once created, it will live throughout the span of your program. Kotlin borrows the approach found in Scala. Here is how you can define a singleton in Kotlin:

Kotlin




object Singleton{
  private var count = 0
  fun doSomething():Unit {
    println("Calling a doSomething (${++count} call/-s in total)")
  }
 }


From any function, you can now call Singleton.doSomething, and each time, you will see the counter increasing. If you were to look at the bytecode produced, you will find out the compiler is doing some of the work for us once again:

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 "<init>":()V
 6: return
}

We have left out the code produced for our doSomething method since it is not the focus of this topic. The compiler once again has created a class and marked it final. Furthermore, it has introduced a member called INSTANCE and has marked it static. The interesting part is at the end of the listing where you see the static{}; entry. This is the class initializer, and it is called only once, JVM will make sure this happens, before:

  • An instance of the class is created
  • A static method of the class is invoked
  • A static field of the class is assigned
  • A non-constant static field is used
  • An assert statement lexically nested within the class is executed for a top-level class

In this case, the code is called before the first call to doSomething because we access the static member INSTANCE (see the following getstatic bytecode routine). If we were to call this method twice, we would get the following bytecode:

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

You can see that on both occasions doSomething is called a virtual method. The reason is you can create a singleton that inherits from a given class, as in the example here:

Kotlin




open class SingletonParent(var x:Int){
  fun something():Unit{
    println("X=$x")
  }
}
object SingletonDerive:SingletonParent(10){}


 
 

There is a way to call a static method as you would do in Java. To achieve this, you will have to place your object within a class and mark it as a companion object. This concept of a companion object will be familiar to someone with at least entry-level knowledge of Scala. The following example uses the factory design pattern to construct an instance of Student: 

 

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)
    }
  }
}


As you can see, the constructor for the Student type has been marked private. Thus, it can’t be invoked from anywhere apart from inside the Student class or the companion object. The companion class has full visibility for all the methods and members of the Student. From the code, you will need to call Student.create(“Jack Wallace”) to create a new instance of Student. If you look in the build output, you will notice there are two classes generated for Student: one is Student.class and the other is Student$Companion.class. Let’s see how the call to Student.create gets translated into bytecode:

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
}

In line 6, you will notice there is a call for a static member getstatic. As you can probably imagine, there is a static field added to the Student class of the type Student.Companion:

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."<init>":(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 "<init>":(Ljava/lang/String;)V
5: return

This code snippet proves the assumption is correct. You can see the Companion member being added to our class. And yet again, the class gets class initializer code generated to create an instance of our companion class. Student.create is shorthand for writing code such as Student.Companion.create(). If you were trying to create an instance of Student.Companion (that is, val c = Student.Companion), you would get a compilation error. A companion object follows all the inheritance rules. 



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads