Métodos estáticos y objetos complementarios en Kotlin

A diferencia de Java, Kotlin no admite métodos estáticos para una clase. La mayoría de los lectores sabrán que los métodos estáticos no pertenecen a la instancia del objeto sino al tipo en sí. En Kotlin, es recomendable definir métodos a nivel de paquete para lograr la funcionalidad de los métodos estáticos. Definamos un nuevo archivo Kotlin y asígnele el nombre Estático. Dentro de este archivo, colocaremos el código para una función que devolverá el primer carácter de la string de entrada (si la entrada está vacía, se generará una excepción), que es la siguiente:

Kotlin

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

Luego, en su código, simplemente puede llamar a showFirstCharacter («¡Kotlin es genial!»). El compilador está aquí para hacer parte del trabajo por usted. Usando javap, podemos echar un vistazo al código de bytes generado. Simplemente ejecute javap -c StaticKt.class para obtener el código producido por el compilador:

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
}

Como puede ver en la impresión, el compilador generó una clase para nosotros y la marcó como final; no se puede heredar, como ya sabes. Dentro de esta clase, el compilador ha agregado la función que definimos. Llamemos a este método desde el punto de entrada del programa y nuevamente usando la utilidad javap podemos ver cómo se ve el código de bytes:

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
}

La mayor parte del código de bytes se ha omitido por simplicidad, pero en la línea 20 puede ver que hay una llamada a nuestro método; en particular, la llamada se realiza a través de la rutina de invocación estática .

No podemos hablar de métodos estáticos y no incluir singletons en la discusión. Un singleton es un patrón de diseño que limita la instanciación de una clase dada a una sola instancia. Una vez creado, vivirá a lo largo de su programa. Kotlin toma prestado el enfoque que se encuentra en Scala. Así es como puede definir un singleton en Kotlin:

Kotlin

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

Desde cualquier función, ahora puede llamar a Singleton.doSomething y, cada vez, verá que el contador aumenta. Si mirara el código de bytes producido, descubriría que el compilador está haciendo parte del trabajo por nosotros una vez más:

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
}

Hemos omitido el código producido para nuestro método doSomething ya que no es el enfoque de este tema. El compilador una vez más ha creado una clase y la ha marcado como final. Además, ha introducido un miembro llamado INSTANCIA y lo ha marcado como estático. La parte interesante está al final de la lista donde ves la estática{}; entrada. Este es el inicializador de clase, y se llama solo una vez, JVM se asegurará de que esto suceda, antes:

  • Se crea una instancia de la clase.
  • Se invoca un método estático de la clase.
  • Se asigna un campo estático de la clase.
  • Se utiliza un campo estático no constante
  • Una declaración de afirmación anidada léxicamente dentro de la clase se ejecuta para una clase de nivel superior

En este caso, el código se llama antes de la primera llamada a hacerAlgo porque accedemos a la INSTANCIA del miembro estático (vea la siguiente rutina getstatic bytecode). Si tuviéramos que llamar a este método dos veces, obtendríamos el siguiente código de bytes:

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

Puede ver que en ambas ocasiones doSomething se denomina método virtual. La razón es que puede crear un singleton que herede de una clase dada, como en el ejemplo aquí:

Kotlin

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

Hay una manera de llamar a un método estático como lo haría en Java. Para lograr esto, deberá colocar su objeto dentro de una clase y marcarlo como un objeto complementario. Este concepto de un objeto complementario le resultará familiar a alguien con al menos un conocimiento básico de Scala. El siguiente ejemplo usa el patrón de diseño de fábrica para construir una instancia de 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)
    }
  }
}

Como puede ver, el constructor del tipo Estudiante se ha marcado como privado. Por lo tanto, no se puede invocar desde ningún lugar que no sea dentro de la clase Student o el objeto complementario. La clase complementaria tiene visibilidad completa para todos los métodos y miembros del Estudiante. Desde el código, deberá llamar a Student.create («Jack Wallace») para crear una nueva instancia de Student. Si observa el resultado de la compilación, notará que se generan dos clases para Student: una es Student.class y la otra es Student$Companion.class . Veamos cómo se traduce la llamada a Student.create a 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
}

En la línea 6, notará que hay una llamada para un miembro estático getstatic . Como probablemente pueda imaginar, hay un campo estático agregado a la clase Student del tipo 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

Este fragmento de código demuestra que la suposición es correcta. Puede ver cómo se agrega el miembro Companion a nuestra clase. Y una vez más, la clase obtiene un código de inicialización de clase generado para crear una instancia de nuestra clase complementaria. Student.create es una abreviatura para escribir código como Student.Companion.create() . Si intentara crear una instancia de Student.Companion (es decir, val c = Student.Companion ), obtendría un error de compilación. Un objeto complementario sigue todas las reglas de herencia. 

Publicación traducida automáticamente

Artículo escrito por eralokyadav2019 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *