Introducción a las clases en Kotlin

Clases en Kotlin

Kotlin, como lenguaje de Programación Orientado a Objetos (POO u OOP según sus siglas en inglés), utiliza las clases para definir las propiedades y los comportamientos de un tipo de objeto y declara objetos que son instancias de una clase.

Las clases en Kotlin son declaradas usando la palabra reservada class:
class Libro { ... }
La declaración de una clase consiste, en este orden, en un nombre, un encabezado y un cuerpo. El nombre identifica a la clase, le sigue un encabezado opcional que puede especificar un constructor y parámetros, y termina con el cuerpo de la clase entre llaves donde se sitúan las propiedades y métodos si los hubiera. Si la clase no tiene cuerpo se pueden omitir las llaves y declararla simplemente así:
class Libro
Y ya podemos instanciar esta clase de esta manera, utilizando su constructor por defecto que se omite:
val libro = Libro()
En Kotlin una clase puede tener un constructor primario y uno o más constructores secundarios. El constructor principal forma parte del encabezado de la clase y va después del nombre de la clase:
class Libro constructor(titulo: String) { ... }
Pero si este constructor principal no tiene anotaciones o modificadores de visibilidad (como private, protected, internal o public que veremos en futuras entradas), la palabra clave constructor se puede omitir:
class Libro(titulo: String) { ... }
Además del constructor primario que no contiene código aparte de sus parámetros, podemos crear otros constructores secundarios:
class Libro {
    
    // propiedades
    var titulo: String
    var isbn: Long
    
    // constructor secundario
    constructor(titulo: String, isbn: Long) {
        this.titulo = title
        this.isbn = isbn
    }
}
También podemos crear uno o varios bloques con la palabra clave init que se ejecutan de manera ordenada según aparecen en el cuerpo de la clase durante la inicialización de una instancia:
class Libro (titulo: String, isbn: Long) {
    
    var titulo: String
    var isbn: Long
 
    init {
       this.titulo = titulo
       this.isbn = isbn
    }
}
Hay que tener en cuenta que el código en los bloques init se convierte en parte del constructor primario, por lo que el código en todos los bloques de inicialización se ejecuta antes que el cuerpo del constructor secundario:
class Constructores {
    var paso = 1
    
    init {
        println("$paso. Bloque init 1")
        paso++
    }
    
    constructor(i: Int) {
        println("$paso. Constructor secundario $i")
    }
    
    init {        
        println("$paso. Bloque init 2")
        paso++
    }
}

fun main() {
    Constructores(1)
}
Este código muestra el siguiente resultado:
1. Bloque init 1
2. Bloque init 2
3. Constructor secundario 1
Además de usarse en los bloques de init, los parámetros del constructor primario también se pueden usar directamente en las propiedades declaradas en el cuerpo de la clase:
class Libro(titulo: String) {
    val libroMay = titulo.toUpperCase()
}
De hecho, podemos declarar propiedades e inicializarlas directamente desde el constructor principal con una sintaxis concisa:
class Persona(val nombre: String, val apellido: String, var edad: Int) { ... }

class Libro(val titulo: String = "")
Y para crear una instancia de una clase, llamamos al constructor como si fuera una función:
val libro = Libro("Don Quijote")
También podemos agregar valores por defecto a cualquiera de las propiedades de la clase directamente dentro del constructor primario. Un par de ejemplos:
class Usuario (var nombre: String, var clave: Long = 123456)

fun main(args: Array<String>) {  

    val usuario1 = Usuario("Torpedo", 238732)
    val usuario2 = Usuario("Demo") // utiliza el valor por defecto de la propiedad clave    
    
    println(usuario1.nombre + ": " + usuario1.clave) // Torpedo: 238732
    println(usuario2.nombre + ": " + usuario2.clave) // Demo: 123456
}
class Usuario (var nombre: String = "Demo", var clave: Long)

fun main(args: Array<String>) {  

    val usuario1 = Usuario("Torpedo", 238732)
    val usuario2 = Usuario(clave=123456) // utiliza el valor por defecto de la propiedad nombre 
    
    println(usuario1.nombre + ": " + usuario1.clave) // Torpedo: 238732
    println(usuario2.nombre + ": " + usuario2.clave) // Demo: 123456
}
Además de constructores y bloques de inicialización, las clases contienen básicamente propiedades y métodos (funciones). Para referirnos a las propiedades (y a los métodos) utilizamos el nombre del objeto seguido por un punto y el nombre de la propiedad, que es lo que se conoce como sintaxis de acceso de propiedad:
class Persona {
    var nombre: String = ""
    var edad: Int = 0
}
 
fun clonarPersona(persona: Persona): Persona {
    val clon = Persona()
    clon.nombre = persona.nombre
    clon.edad = persona.edad
    return clon
}

fun main(args: Array<String>) {  

    val persona1 = Persona()
    persona1.nombre = "Pepe"
    persona1.edad = 32
    val persona2 = clonarPersona(persona1)
    
    println(persona1.nombre) // Pepe
    println(persona2.nombre) // Pepe       
}
Esto mismo lo podríamos escribir así:
class Persona (var nombre: String, var edad: Int)
 
fun clonarPersona(persona: Persona): Persona {
    val clon = Persona(persona.nombre, persona.edad)    
    return clon
}

fun main(args: Array<String>) {  

    val persona1 = Persona("Pepe", 32) // inicializamos directamente desde el constructor    
    val persona2 = clonarPersona(persona1)
    
    println(persona1.nombre) // Pepe
    println(persona2.nombre) // Pepe       
}
Como hemos visto, las propiedades de una clase pueden declararse como mutables usando var o como de solo lectura con val:
class Libro (
    var titulo: String, 
    val isbn: Long
)

fun main(args: Array<String>) {  
    val libro = Libro("Don Quijote", 4771599780007)
    libro.titulo = "Don Quijote de la Mancha" // la propiedad titulo es reasignada a otro valor
    libro.isbn = 599780007123 // error: propiedad de solo lectura no puede ser reasignada
}
También hay que tener en cuenta que Kotlin no permite definir una propiedad y no asignarle ningún valor y que quede con el valor null.

Un ejemplo de clase con propiedades y métodos:
class Bombilla { // creamos la clase Bombilla
    
    // propiedad de clase
    var isOn: Boolean = false

    // métodos de clase (funciones)
    fun encender() {
        isOn = true
    }

    fun apagar() {
        isOn = false
    }

    fun muestraEstado(bombilla: String) {
        if (isOn == true)
            println("La bombilla $bombilla está encendida.")
        else
            println("La bombilla $bombilla está apagada.")
    }
}

fun main(args: Array<String>) {

    // creamos objetos de la clase Bombilla
    val b1 = Bombilla()
    val b2 = Bombilla()

    // invocamos los métodos de la clase para cada objeto
    // métodos que cambian el valor de la propiedad
    b1.encender() 
    b2.apagar()  // no es necesario porque el valor isOn se inicializa en false para cada nuevo objeto
    // métodos que muestran el estado de la proiedad
    b1.muestraEstado("b1")
    b2.muestraEstado("b2")
}
Un ejemplo de una clase con constructor primario con valores por defecto y un bloque inicializador:
fun main(args: Array<String>) {    
    val persona1 = Persona("juan", 25)   
    val persona2 = Persona("pepe")    
    val persona3 = Persona()
}

class Persona(_nombre: String = "DESCONOCIDO", _edad: Int = 0) {
    // es común usar los mismos nombres para el parámetro y la propiedad
    // y para distinguirlos se suele usar un guión bajo
    val nombre = _nombre.capitalize() 
    var edad = _edad

    // bloque inicializador que se ejecuta cuando se instancia un objeto de la clase
    init {
        println("Nombre: $nombre - Edad: $edad")
    }
}

Comentarios

Entradas populares

I/O: entrada y salida de datos en consola

Recursos gratis para aprender Kotlin

Lectura y escritura de archivos