Kotlin

Kotlin

Tutorial Kotlin: Clases y objetos

Aprende a definir clases y objetos en Kotlin: propiedades, métodos, constructores y más. Optimiza tu programación con conceptos de orientación a objetos.

Aprende Kotlin GRATIS y certifícate

Definición de clases y creación de objetos

En Kotlin, una clase es una estructura que permite crear nuevos tipos de datos agrupando propiedades y funciones relacionadas. Definir una clase es esencial para representar objetos del mundo real y establecer su comportamiento dentro del programa.

Para definir una clase básica en Kotlin, se utiliza la palabra clave class seguida del nombre de la clase. Por ejemplo:

class Persona

Este fragmento de código declara una clase llamada Persona sin propiedades ni funciones.

Para crear un objeto o instancia de una clase, se utiliza el operador () después del nombre de la clase. Por ejemplo:

val persona = Persona()

Aquí, persona es un objeto de tipo Persona, también se puede decir que persona es una instancia de la clase Persona.

Las clases también pueden tener un cuerpo entre llaves {} donde se declaran propiedades y funciones adicionales. Si no hay necesidad de propiedades o funciones extra, las llaves pueden omitirse.

En resumen, la definición de clases y la creación de objetos en Kotlin es sencilla y concisa, facilitando una programación orientada a objetos efectiva y moderna.

Propiedades y métodos de las clases

En Kotlin, las propiedades y los métodos son elementos esenciales que definen el estado y el comportamiento de una clase. Las propiedades representan los datos o atributos que posee un objeto, mientras que los métodos son funciones que describen las acciones que este puede realizar.

Para declarar una propiedad dentro de una clase, se utilizan las palabras clave var o val, según se requiera que la propiedad sea mutable o inmutable. Por ejemplo:

class Persona {
    var nombre: String = "Desconocido"
    val edad: Int = 0
}

En este ejemplo, nombre es una propiedad mutable cuyo valor puede cambiarse después de la inicialización, mientras que edad es una propiedad inmutable que no puede modificarse una vez asignada.

Las propiedades en Kotlin vienen acompañadas de getters y setters generados automáticamente. El getter permite acceder al valor de la propiedad y el setter permite asignarle un nuevo valor (solo para propiedades mutables). Es posible personalizar estos accesores si se necesita añadir lógica adicional:

class Empleado {
    var salario: Double = 0.0
        set(value) {
            field = if (value >= 0) value else 0.0
        }
}

Aquí, el setter de salario verifica que el valor asignado no sea negativo, asegurando que la propiedad mantenga un estado válido.

Además de las propiedades, las clases pueden tener métodos que definen su comportamiento. Un método es una función declarada dentro de la clase y puede acceder a las propiedades y otros métodos de la misma:

class Circulo(val radio: Double) {
    fun calcularArea(): Double {
        return Math.PI * radio * radio
    }
}

El método calcularArea utiliza la propiedad radio para calcular y devolver el área del círculo. Los métodos permiten encapsular operaciones relacionadas con la clase y sus propiedades.

Es importante considerar los modificadores de visibilidad al declarar propiedades y métodos. Estos determinan desde dónde pueden ser accedidos:

  • public: Accesible desde cualquier parte del código (es el valor por defecto).
  • private: Solo accesible dentro de la clase donde se declara.
  • protected: Accesible dentro de la clase y sus subclases.
  • internal: Accesible dentro del mismo módulo.

Por ejemplo:

class CuentaBancaria {
    private var saldo: Double = 0.0

    fun depositar(cantidad: Double) {
        saldo += cantidad
    }

    fun obtenerSaldo(): Double {
        return saldo
    }
}

En este caso, la propiedad saldo es privada, lo que significa que no puede ser accedida directamente desde fuera de la clase CuentaBancaria, promoviendo el encapsulamiento de los datos.

Las propiedades también pueden ser calculadas, es decir, su valor se define mediante una expresión en lugar de almacenarse en memoria:

class Rectangulo(val ancho: Double, val alto: Double) {
    val area: Double
        get() = ancho * alto
}

La propiedad area es calculada cada vez que se accede a ella, utilizando los valores de ancho y alto.

El método millasAKilometros puede ser llamado sin crear una instancia de Conversor, usando Conversor.millasAKilometros(10.0).

Además, Kotlin permite el uso de propiedades delegadas, donde el comportamiento de una propiedad es delegado a otra clase. Un ejemplo común es la delegación lazy, que retrasa la inicialización de una propiedad hasta su primer acceso:

class DatosUsuario {
    val perfilCompleto: Perfil by lazy {
        cargarPerfilDesdeBaseDeDatos()
    }
}

Con lazy, la propiedad perfilCompleto no se inicializa hasta que se utiliza por primera vez, optimizando así los recursos.

Por último, es posible sobrescribir propiedades y métodos en clases derivadas utilizando la palabra clave open en la clase base y override en la clase hija:

open class Animal {
    open fun hacerSonido() {
        println("El animal hace un sonido")
    }
}

class Perro : Animal() {
    override fun hacerSonido() {
        println("El perro ladra")
    }
}

El método hacerSonido de la clase Perro sobrescribe al de la clase Animal, proporcionando una implementación específica.

Comprender y utilizar adecuadamente las propiedades y métodos es fundamental para aprovechar al máximo las capacidades de Kotlin en programación orientada a objetos, permitiendo crear clases robustas y funcionales.

Inicialización de objetos y constructores

La inicialización de objetos en Kotlin se realiza a través de constructores, que son funciones especiales destinadas a crear instancias de una clase. Kotlin ofrece una sintaxis concisa para definir constructores, permitiendo establecer propiedades y ejecutar código durante la inicialización.

En Kotlin, las clases pueden tener un constructor primario y uno o más constructores secundarios. El constructor primario forma parte de la definición de la clase y puede recibir parámetros que inicialicen las propiedades:

class Persona(val nombre: String, var edad: Int)

En este ejemplo, Persona tiene un constructor primario con dos parámetros: nombre y edad, que se asignan directamente como propiedades de la clase.

Si se necesita ejecutar código adicional durante la inicialización, se pueden utilizar bloques de inicialización con la palabra clave init:

class Persona(val nombre: String) {
    val saludo: String

    init {
        saludo = "Hola, mi nombre es $nombre"
    }
}

El bloque init se ejecuta inmediatamente después del constructor primario y permite ejecutar código que no sea directamente la asignación de propiedades.

Además del constructor primario, las clases pueden definir constructores secundarios usando la palabra clave constructor:

class Persona {
    var nombre: String
    var edad: Int

    constructor(nombre: String) {
        this.nombre = nombre
        this.edad = 0
    }

    constructor(nombre: String, edad: Int) {
        this.nombre = nombre
        this.edad = edad
    }
}

En este caso, Persona tiene dos constructores secundarios que ofrecen diferentes formas de inicializar una instancia. Es importante destacar que si hay un constructor primario, los constructores secundarios deben delegar en él utilizando this:

class Persona(val nombre: String) {
    var edad: Int = 0

    constructor(nombre: String, edad: Int) : this(nombre) {
        this.edad = edad
    }
}

Aquí, el constructor secundario constructor(nombre: String, edad: Int) delega en el constructor primario this(nombre) y luego inicializa edad.

Las propiedades pueden tener valores predeterminados, lo que simplifica la inicialización:

class Persona(val nombre: String, var edad: Int = 0)

De esta manera, se puede crear una instancia sin especificar todos los parámetros:

val persona = Persona("Ana")

La propiedad edad tomará el valor predeterminado 0 si no se proporciona.

Para propiedades que no pueden inicializarse en el constructor, Kotlin ofrece la palabra clave lateinit para propiedades de inicialización tardía:

class Empleado {
    lateinit var departamento: String

    fun asignarDepartamento(dep: String) {
        departamento = dep
    }

    fun imprimirDepartamento() {
        if (::departamento.isInitialized) {
            println("Departamento: $departamento")
        } else {
            println("Departamento no asignado")
        }
    }
}

Es importante comprobar si la propiedad departamento ha sido inicializada antes de usarla, utilizando ::departamento.isInitialized.

Otra opción es utilizar propiedades perezosas con by lazy, que se inicializan en el primer acceso:

class Configuracion {
    val configuracionCompleja: String by lazy {
        cargarConfiguracion()
    }

    private fun cargarConfiguracion(): String {
        // Código para cargar la configuración
        return "Configuración cargada"
    }
}

La inicialización perezosa es útil para retrasar operaciones costosas hasta que sean necesarias.

En clases que heredan de otras, es posible inicializar la clase base directamente desde el constructor primario:

open class Vehiculo(val marca: String)

class Coche(marca: String, val modelo: String) : Vehiculo(marca)

La clase Coche llama al constructor de Vehiculo pasando marca como argumento, asegurando la inicialización correcta de la clase base.

Los bloques de inicialización y los constructores se ejecutan en un orden específico: primero se llama al constructor primario de la clase base, luego se ejecutan los bloques init de la clase derivada y, finalmente, los constructores secundarios.

Es posible usar expresiones de inicialización directamente en las propiedades:

class Rectangulo(val ancho: Double, val alto: Double) {
    val area = ancho * alto
}

Esta sintaxis permite inicializar propiedades sin necesidad de bloques init o constructores adicionales.

En resumen, Kotlin proporciona mecanismos flexibles para la inicialización de objetos a través de constructores primarios y secundarios, bloques init, propiedades con valores predeterminados y técnicas de inicialización tardía o perezosa. Comprender estos conceptos es esencial para crear clases robustas y garantizar que los objetos se instancien correctamente.

Clases anidadas e internas

En Kotlin, es posible definir clases anidadas dentro de otras clases. Una clase anidada es una clase declarada dentro del cuerpo de otra clase y, por defecto, es estática, lo que significa que no mantiene una referencia a la instancia de la clase externa.

Para crear una clase anidada, simplemente se declara una clase dentro de otra:

class Contenedor {
    class Anidada {
        fun mostrarMensaje() = "Este es un mensaje desde la clase anidada."
    }
}

Para instanciar una clase anidada, se accede a ella a través del nombre de la clase externa:

fun main() {
    val instancia = Contenedor.Anidada()
    println(instancia.mostrarMensaje())
}

La salida de este programa será:

Este es un mensaje desde la clase anidada.

Al ser estática, la clase anidada no puede acceder directamente a los miembros de la clase externa. Si se necesita que la clase interna acceda a los miembros de la clase externa, se debe definir como una clase interna utilizando la palabra clave inner:

class Contenedor {
    private val mensaje = "Hola desde la clase externa."

    inner class Interna {
        fun mostrarMensaje() = "Mensaje: $mensaje"
    }
}

En este caso, la clase Interna es una clase interna que puede acceder a la propiedad mensaje de la clase Contenedor. Para crear una instancia de una clase interna, es necesario tener una instancia de la clase externa:

fun main() {
    val contenedor = Contenedor()
    val interna = contenedor.Interna()
    println(interna.mostrarMensaje())
}

La salida será:

Mensaje: Hola desde la clase externa.

La palabra clave inner es esencial para que la clase interna tenga acceso a los miembros de la clase externa. Sin inner, la clase sería anidada y no podría interactuar con la instancia de la clase que la contiene.

Dentro de una clase interna, es posible referirse a la instancia de la clase externa utilizando this@NombreClase:

class Contenedor(val valor: Int) {
    inner class Interna {
        fun duplicarValor() = this@Contenedor.valor * 2
    }
}

Aquí, this@Contenedor se refiere a la instancia de Contenedor, permitiendo acceder a su propiedad valor.

Las clases anidadas e internas son útiles para organizar el código y representar relaciones lógicas. Por ejemplo, si una clase solo tiene sentido en el contexto de otra, anidarla mejora la legibilidad y mantiene el código agrupado.

Es importante considerar que las clases internas mantienen una referencia a la instancia de la clase externa, lo que puede implicar consideraciones de rendimiento y gestión de memoria. Por ello, se recomienda utilizar clases internas únicamente cuando sea necesario acceder a los miembros de la clase externa.

Las clases anidadas pueden contener miembros estáticos. Si se necesita definir constantes o funciones relacionadas con la clase externa pero que no requieren una instancia de ella, se pueden utilizar objetos acompañantes (companion object):

class Matematica {
    class Constantes {
        companion object {
            const val PI = 3.1416
            const val E = 2.7182
        }
    }
}

Para acceder a estas constantes, se utiliza el nombre completo:

fun main() {
    println("El valor de PI es ${Matematica.Constantes.PI}")
}

Las clases internas pueden acceder a todos los miembros de la clase externa, incluso si son privados:

class Banco {
    private val tasaInteres = 0.05

    inner class Cuenta {
        fun calcularInteres(monto: Double) = monto * tasaInteres
    }
}

La clase Cuenta puede utilizar tasaInteres, a pesar de que es una propiedad privada de Banco.

También es posible anidar clases dentro de funciones locales. Estas se llaman clases locales y su ámbito está limitado a la función donde se declaran:

fun operacion() {
    class Suma(val a: Int, val b: Int) {
        fun resultado() = a + b
    }

    val suma = Suma(5, 3)
    println("El resultado es ${suma.resultado()}")
}

Las clases locales pueden ser útiles para encapsular lógica específica dentro de una función sin exponerla al resto del programa.

En resumen, las clases anidadas e internas proporcionan herramientas poderosas para estructurar y organizar el código en Kotlin. Las clases anidadas permiten agrupar clases relacionadas sin necesidad de acceder a los miembros de la clase externa, mientras que las clases internas facilitan la interacción directa con la instancia de la clase que las contiene.

Aprende Kotlin GRATIS online

Ejercicios de esta lección Clases y objetos

Evalúa tus conocimientos de esta lección Clases y objetos con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Todas las lecciones de Kotlin

Accede a todas las lecciones de Kotlin y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Kotlin

Kotlin

Introducción Y Entorno

Instalación Y Primer Programa De Kotlin

Kotlin

Introducción Y Entorno

Tipos De Datos, Variables Y Constantes

Kotlin

Sintaxis

Operadores Y Expresiones

Kotlin

Sintaxis

Cadenas De Texto Y Manipulación

Kotlin

Sintaxis

Estructuras De Control

Kotlin

Sintaxis

Funciones Y Llamada De Funciones

Kotlin

Sintaxis

Clases Y Objetos

Kotlin

Programación Orientada A Objetos

Herencia Y Polimorfismo

Kotlin

Programación Orientada A Objetos

Interfaces Y Clases Abstractas

Kotlin

Programación Orientada A Objetos

Data Classes Y Destructuring

Kotlin

Programación Orientada A Objetos

Tipos Genéricos Y Varianza

Kotlin

Programación Orientada A Objetos

Listas, Conjuntos Y Mapas

Kotlin

Estructuras De Datos

Introducción A La Programación Funcional

Kotlin

Programación Funcional

Funciones De Primera Clase Y De Orden Superior

Kotlin

Programación Funcional

Inmutabilidad Y Datos Inmutables

Kotlin

Programación Funcional

Composición De Funciones

Kotlin

Programación Funcional

Monads Y Manejo Funcional De Errores

Kotlin

Programación Funcional

Operaciones Funcionales En Colecciones

Kotlin

Programación Funcional

Transformaciones En Monads Y Functors

Kotlin

Programación Funcional

Funciones Parciales Y Currificación

Kotlin

Programación Funcional

Introducción A Las Corutinas

Kotlin

Coroutines Y Asincronía

Asincronía Con Suspend, Async Y Await

Kotlin

Coroutines Y Asincronía

Concurrencia Funcional

Kotlin

Coroutines Y Asincronía

Evaluación

Kotlin

Evaluación

Accede GRATIS a Kotlin y certifícate

Certificados de superación de Kotlin

Supera todos los ejercicios de programación del curso de Kotlin y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  1. Comprender la definición y creación de clases y objetos en Kotlin.
  2. Manejar propiedades y métodos, incluyendo getters y setters personalizados.
  3. Aplicar modificadores de visibilidad para el encapsulamiento.
  4. Utilizar constructores primarios y secundarios eficazmente.
  5. Implementar clases anidadas e internas, entendiendo su contexto y uso.
  6. Emplear inicialización perezosa y tardía de propiedades.