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
valsobrevarpara 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.