Volver a Kotlin Básico

Introducción

Kotlin simplifica la creación de clases con sintaxis concisa, data classes para contenedores de datos y sealed classes para jerarquías restringidas.

Clases Básicas

// Clase simple class Persona { var nombre: String = "" var edad: Int = 0 } // Con constructor primario class Persona(val nombre: String, val edad: Int) // Con bloque init class Persona(val nombre: String) { val longitudNombre: Int init { println("Creando persona: $nombre") longitudNombre = nombre.length } } // Con parámetros por defecto class Persona( val nombre: String = "Desconocido", val edad: Int = 0, val pais: String = "Desconocido" )

Propiedades

class Rectangulo(val ancho: Int, val alto: Int) { // Propiedad computada val area: Int get() = ancho * alto // Propiedad con getter personalizado val esCuadrado: Boolean get() = ancho == alto // Propiedad mutable con getter y setter var descripcion: String = "" get() = field.ifEmpty { "Rectángulo ${ancho}x${alto}" } set(value) { field = value.trim() } }

Data Classes

Perfectas para contener datos:

data class Usuario( val id: Int, val nombre: String, val email: String ) val usuario1 = Usuario(1, "Alicia", "[email protected]") val usuario2 = usuario1.copy(nombre = "Bob") // Copiar con modificación // Métodos auto-generados println(usuario1.toString()) // Usuario(id=1, nombre=Alicia, [email protected]) println(usuario1 == usuario2) // Igualdad estructural println(usuario1 === usuario2) // Igualdad referencial // Desestructuración val (id, nombre, email) = usuario1 println("Usuario $nombre tiene id $id")

Sealed Classes

Jerarquías de clases restringidas:

sealed class Resultado { data class Exito(val datos: String) : Resultado() data class Error(val mensaje: String) : Resultado() object Cargando : Resultado() } fun manejarResultado(resultado: Resultado): String { return when (resultado) { is Resultado.Exito -> "Datos: ${resultado.datos}" is Resultado.Error -> "Error: ${resultado.mensaje}" Resultado.Cargando -> "Cargando..." // No necesita 'else' - el compilador conoce todos los casos } }

Declaraciones Object

Singletons:

object GestorBaseDatos { private val conexiones = mutableListOf<Conexion>() fun conectar() { println("Conectando a base de datos") } fun obtenerCantidadConexiones() = conexiones.size } // Uso GestorBaseDatos.conectar()

Companion Objects

Miembros tipo estático:

class Usuario(val nombre: String) { companion object { const val EDAD_MAXIMA = 150 fun crear(nombre: String): Usuario { return Usuario(nombre) } } } // Uso val usuario = Usuario.crear("Alicia") println(Usuario.EDAD_MAXIMA)

Modificadores de Visibilidad

class MiClase { private val interno = "Solo visible en esta clase" protected val heredado = "Visible en subclases" internal val modulo = "Visible en el mismo módulo" public val todos = "Visible en todas partes" // Por defecto }

Herencia

open class Animal(val nombre: String) { open fun hacerSonido() { println("Algún sonido") } } class Perro(nombre: String) : Animal(nombre) { override fun hacerSonido() { println("¡Guau!") } } class Gato(nombre: String, val raza: String) : Animal(nombre) { override fun hacerSonido() { println("¡Miau!") } }

Clases Abstractas

abstract class Forma { abstract val area: Double abstract fun dibujar() fun describir() { println("Esta es una forma con área $area") } } class Circulo(val radio: Double) : Forma() { override val area: Double get() = Math.PI * radio * radio override fun dibujar() { println("Dibujando círculo") } }

Interfaces

interface Clickeable { fun click() fun mostrarInfo() { println("Elemento clickeable") // Implementación por defecto } } interface Arrastrable { fun arrastrar() } class Boton : Clickeable, Arrastrable { override fun click() { println("Botón clickeado") } override fun arrastrar() { println("Botón arrastrado") } }

Enum Classes

enum class Direccion { NORTE, SUR, ESTE, OESTE } enum class Color(val rgb: Int) { ROJO(0xFF0000), VERDE(0x00FF00), AZUL(0x0000FF) } // Con métodos enum class Estado { ACTIVO { override fun mensaje() = "Actualmente activo" }, INACTIVO { override fun mensaje() = "Actualmente inactivo" }; abstract fun mensaje(): String }

Clases Anidadas e Internas

class Externa { private val bar = 1 // Clase anidada (no tiene acceso a externa) class Anidada { fun foo() = 2 } // Clase interna (tiene acceso a externa) inner class Interna { fun foo() = bar } } val anidada = Externa.Anidada().foo() // 2 val interna = Externa().Interna().foo() // 1

Ejemplos Prácticos

Manejo de respuestas API:

sealed class RespuestaApi<out T> { data class Exito<T>(val datos: T) : RespuestaApi<T>() data class Error(val codigo: Int, val mensaje: String) : RespuestaApi<Nothing>() object Cargando : RespuestaApi<Nothing>() } fun manejarRespuesta(respuesta: RespuestaApi<Usuario>) { when (respuesta) { is RespuestaApi.Exito -> println("Usuario: ${respuesta.datos.nombre}") is RespuestaApi.Error -> println("Error ${respuesta.codigo}: ${respuesta.mensaje}") RespuestaApi.Cargando -> println("Cargando...") } }

Clase de configuración:

data class ConfigBaseDatos( val host: String = "localhost", val puerto: Int = 5432, val usuario: String, val contrasena: String, val baseDatos: String, val maxConexiones: Int = 10 ) { companion object { fun desdeEntorno(): ConfigBaseDatos { return ConfigBaseDatos( host = System.getenv("DB_HOST") ?: "localhost", puerto = System.getenv("DB_PORT")?.toInt() ?: 5432, usuario = System.getenv("DB_USER") ?: "admin", contrasena = System.getenv("DB_PASS") ?: "", baseDatos = System.getenv("DB_NAME") ?: "miapp" ) } } fun aCadenaConexion(): String { return "postgresql://$usuario:$contrasena@$host:$puerto/$baseDatos" } }

Buenas Prácticas

  • Usa data classes para DTOs y objetos de valor
  • Usa sealed classes para jerarquías de tipos restringidas
  • Prefiere val sobre var para inmutabilidad
  • Usa companion objects para métodos de fábrica
  • Mantén las clases enfocadas (Responsabilidad Única)
  • Usa interfaces para comportamiento, clases abstractas para estado compartido

Conclusión

Las características de clases de Kotlin reducen el código repetitivo mientras proporcionan abstracciones poderosas. Las data classes, sealed classes y declaraciones object hacen los patrones comunes simples y seguros.

En el próximo capítulo, exploraremos colecciones en profundidad.