Herramientas de usuario

Herramientas del sitio


anexos:kotlin2

Programación Orientada a Objetos en Kotlin

Clases

La Programación Orientada a Objetos (POO) es un paradigma de programación basado en la utilización de objetos.

Un objeto agrupa principalmente:

  • Datos, representados mediante propiedades.
  • Comportamiento, representado mediante funciones.

Por ejemplo, si queremos representar un coche podríamos tener:

Coche

Propiedades:
marca
modelo
velocidad

Funciones:
acelerar()
frenar()
mostrarInformacion() 

Para definir este tipo de objetos utilizaremos clases.

Clases y objetos

Una clase es una plantilla que define las características y el comportamiento que tendrán los objetos creados a partir de ella.

Por ejemplo:

class Coche {
    var marca = ""
    var modelo = ""
    var velocidad = 0
}

Esta clase define cómo serán los objetos de tipo `Coche`.

Sin embargo, declarar una clase no crea ningún coche.

Para trabajar con ella debemos crear objetos o instancias de la clase.

Clase frente a objeto

Podemos entender una clase como un plano o plantilla y un objeto como una realización concreta de ese plano.

Una misma clase puede utilizarse para crear muchos objetos diferentes.

Declaración de una clase

Para declarar una clase utilizamos la palabra reservada `class` seguida del nombre de la clase:

class Persona {
 
} 

Por convenio, los nombres de las clases comienzan con mayúscula:

class Persona
class Alumno
class Producto
class CuentaBancaria

Una clase puede estar formada por dos partes:

  • Cabecera de la clase.
  • Cuerpo de la clase.

Por ejemplo:

class Persona(val nombre: String) {
 
```
var edad = 0
 
fun saludar() {
    println("Hola, soy $nombre")
}
```
 
} 

La cabecera es:

class Persona(val nombre: String)

El cuerpo es todo lo que aparece entre `{ }`:

{
    var edad = 0
 
```
fun saludar() {
    println("Hola, soy $nombre")
}
```
 
} 

El cuerpo puede contener, entre otros elementos:

  • Propiedades.
  • Funciones.
  • Bloques de inicialización.
  • Constructores secundarios.
  • Otras clases u objetos.

Si una clase no necesita cuerpo podemos omitir las llaves:

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

Crear objetos

Para crear un objeto escribimos el nombre de la clase seguido de paréntesis:

val persona = Persona("Ana", 20)

A esta operación la denominamos instanciar una clase.

`persona` es ahora un objeto o instancia de `Persona`.

A diferencia de otros lenguajes orientados a objetos como Java, en Kotlin no se utiliza la palabra `new` para crear objetos.

Por ejemplo:

// Kotlin
val persona = Persona("Ana", 20)

No escribimos:

// Incorrecto en Kotlin
// val persona = new Persona("Ana", 20)

Podemos crear múltiples objetos de una misma clase:

val persona1 = Persona("Ana", 20)
val persona2 = Persona("Carlos", 25)
val persona3 = Persona("Marta", 19)

Todos son objetos diferentes, aunque hayan sido creados utilizando la misma clase.

Propiedades

Las variables que forman parte de una clase reciben normalmente el nombre de propiedades.

Por ejemplo:

class Coche {
    var marca = ""
    var modelo = ""
    var velocidad = 0
}

La clase `Coche` tiene tres propiedades:

marca
modelo
velocidad

Podemos acceder a ellas utilizando el operador `.`:

val coche = Coche()
 
coche.marca = "Toyota"
coche.modelo = "Corolla"
coche.velocidad = 50
 
println(coche.marca)
println(coche.modelo)
println(coche.velocidad) 

Propiedades val y var

Al igual que las variables normales, las propiedades pueden declararse utilizando `val` o `var`.

Por ejemplo:

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

En este caso:

  • `nombre` es una propiedad que no puede reasignarse.
  • `edad` es una propiedad que puede modificarse.
val persona = Persona("Ana", 20)
 
persona.edad = 21 

Esto es correcto.

Sin embargo:

// ERROR
// persona.nombre = "Marta"

no está permitido porque `nombre` fue declarado mediante `val`.

Al igual que con las variables normales, es recomendable utilizar `val` cuando una propiedad no necesite ser reasignada.

Funciones de una clase

Las clases también pueden contener funciones.

Estas funciones representan el comportamiento de los objetos.

Por ejemplo:

class Coche(
    val marca: String,
    val modelo: String,
    var velocidad: Int
) {
 
```
fun acelerar() {
    velocidad += 10
}
 
fun frenar() {
    velocidad -= 10
}
 
fun mostrarInformacion() {
    println("$marca $modelo - $velocidad km/h")
}
```
 
} 

Podemos utilizar estas funciones mediante el operador `.`:

fun main() {
 
```
val coche = Coche(
    "Toyota",
    "Corolla",
    0
)
 
coche.acelerar()
coche.acelerar()
 
coche.mostrarInformacion()
```
 
} 

Resultado:

Toyota Corolla - 20 km/h

Las funciones pueden acceder directamente a las propiedades de su objeto:

fun acelerar() {
    velocidad += 10
}

No necesitamos pasar `velocidad` como parámetro porque pertenece al propio objeto.

Constructor primario

Cuando creamos un objeto normalmente queremos proporcionar sus datos iniciales.

Para ello utilizamos un constructor.

La forma más habitual en Kotlin es utilizar un constructor primario, que se declara directamente en la cabecera de la clase:

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

Ahora debemos proporcionar esos valores al crear el objeto:

val persona = Persona("Ana", 20)

Podemos acceder posteriormente a ellos:

println(persona.nombre)
println(persona.edad)

Resultado:

Ana
20

La sintaxis completa del constructor podría escribirse utilizando la palabra `constructor`:

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

Sin embargo, normalmente podemos omitirla:

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

Esta es la forma que utilizaremos habitualmente.

Parámetros y propiedades del constructor

Existe una diferencia importante entre escribir:

class Persona(nombre: String)

y:

class Persona(val nombre: String)

En el segundo caso `nombre` se convierte en una propiedad del objeto.

Por ello podemos hacer:

val persona = Persona("Ana")
 
println(persona.nombre) 

Sin `val` o `var`, el parámetro del constructor no se convierte automáticamente en una propiedad accesible del objeto.

Por tanto:

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

es una forma muy habitual de declarar simultáneamente:

  • Los parámetros necesarios para crear el objeto.
  • Las propiedades que almacenará.

Valores por defecto

Los parámetros del constructor pueden tener valores por defecto:

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

Ahora podemos crear objetos de diferentes maneras:

val persona1 = Persona()
 
val persona2 = Persona("Ana")
 
val persona3 = Persona("Carlos", 25) 

Los valores serán:

persona1 -> nombre = "Desconocido", edad = 0

persona2 -> nombre = "Ana", edad = 0

persona3 -> nombre = "Carlos", edad = 25 

También podemos utilizar argumentos con nombre:

val persona = Persona(
    edad = 25,
    nombre = "Carlos"
)

Propiedades adicionales

No todas las propiedades tienen que declararse en el constructor.

Podemos declarar otras propiedades dentro del cuerpo de la clase:

class Persona(
    val nombre: String,
    val anioNacimiento: Int
) {
 
```
val mayoriaEdad = 18
 
var activo = true
```
 
} 

También podemos calcular una propiedad utilizando los datos recibidos por el constructor:

class Rectangulo(
    val base: Double,
    val altura: Double
) {
 
```
val area = base * altura
```
 
} 

Uso:

val rectangulo = Rectangulo(10.0, 5.0)
 
println(rectangulo.area) 

Resultado:

50.0

Bloque init

En ocasiones necesitamos ejecutar instrucciones cuando se crea un objeto.

Para ello podemos utilizar un bloque `init`.

class Persona(
    val nombre: String,
    var edad: Int
) {
 
```
init {
    println("Creando una nueva persona")
}
```
 
} 

Cuando hacemos:

val persona = Persona("Ana", 20)

se ejecutará automáticamente el bloque `init`.

Podemos acceder desde él a los parámetros y propiedades:

class Persona(
    val nombre: String,
    var edad: Int
) {
 
```
init {
    println("Creando a $nombre")
    println("Edad inicial: $edad")
}
```
 
} 

Validación durante la inicialización

Uno de los usos del bloque `init` es comprobar que los datos utilizados para crear un objeto sean correctos.

Por ejemplo:

class Persona(
    val nombre: String,
    var edad: Int
) {
 
```
init {
    require(edad >= 0) {
        "La edad no puede ser negativa"
    }
}
```
 
} 

Esto sería válido:

val persona = Persona("Ana", 20)

Pero:

val persona = Persona("Ana", -5)

provocaría un error porque no se cumple la condición establecida mediante `require()`.

Otro ejemplo:

class Producto(
    val nombre: String,
    val precio: Double
) {
 
```
init {
    require(nombre.isNotBlank()) {
        "El nombre no puede estar vacío"
    }
 
    require(precio >= 0) {
        "El precio no puede ser negativo"
    }
}
```
 
} 

`require()` permite indicar una condición que debe cumplirse.

Si la condición es falsa, Kotlin lanza una excepción indicando que el argumento proporcionado no es válido.

No es necesario profundizar todavía en las excepciones para poder utilizar esta validación básica.

Constructores secundarios

Una clase puede tener además constructores secundarios.

Se declaran mediante `constructor` dentro del cuerpo de la clase.

Por ejemplo:

class Persona(
    val nombre: String,
    var edad: Int
) {
 
```
constructor(nombre: String) : this(nombre, 0)
```
 
} 

Ahora podemos crear:

val persona1 = Persona("Ana", 20)
 
val persona2 = Persona("Carlos") 

En el segundo caso se utiliza el constructor secundario:

constructor(nombre: String) : this(nombre, 0)

que llama al constructor primario proporcionando:

nombre -> valor recibido
edad   -> 0

Si existe un constructor primario, los constructores secundarios deben terminar delegando en él mediante `this(…)`.

No obstante, Kotlin permite solucionar muchas situaciones mediante valores por defecto, por lo que no necesitaremos constructores secundarios con tanta frecuencia como en otros lenguajes.

Por ejemplo, en el caso anterior probablemente sea más sencillo escribir:

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

Ejemplo completo: clase Producto

Vamos a representar un producto de una tienda.

Cada producto tendrá:

  • Nombre.
  • Precio.
  • Stock.

Además podrá:

  • Mostrar su información.
  • Vender unidades.
  • Reponer unidades.
  • Calcular el valor total del stock.
class Producto(
    val nombre: String,
    val precio: Double,
    var stock: Int = 0
) {
 
```
init {
    require(nombre.isNotBlank()) {
        "El nombre no puede estar vacío"
    }
 
    require(precio >= 0) {
        "El precio no puede ser negativo"
    }
 
    require(stock >= 0) {
        "El stock no puede ser negativo"
    }
}
 
fun vender(cantidad: Int) {
 
    if (cantidad <= 0) {
        println("La cantidad debe ser positiva")
    } else if (cantidad > stock) {
        println("No existe stock suficiente")
    } else {
        stock -= cantidad
        println("Venta realizada")
    }
}
 
fun reponer(cantidad: Int) {
 
    if (cantidad > 0) {
        stock += cantidad
    }
}
 
fun calcularValorStock(): Double {
    return precio * stock
}
 
fun mostrarInformacion() {
    println("Producto: $nombre")
    println("Precio: $precio €")
    println("Stock: $stock")
    println("Valor del stock: ${calcularValorStock()} €")
}
```
 
} 

Podemos utilizarla:

fun main() {
 
```
val teclado = Producto(
    nombre = "Teclado mecánico",
    precio = 59.95,
    stock = 10
)
 
teclado.mostrarInformacion()
 
teclado.vender(3)
 
teclado.reponer(5)
 
teclado.mostrarInformacion()
```
 
} 

En este ejemplo podemos identificar claramente los dos elementos principales de un objeto.

Estado del objeto:

nombre
precio
stock

Comportamiento del objeto:

vender()
reponer()
calcularValorStock()
mostrarInformacion()

Referencias entre objetos

Una variable que contiene un objeto contiene una referencia a ese objeto.

Por ejemplo:

val producto1 = Producto(
    "Teclado",
    50.0,
    10
)
 
val producto2 = producto1 

En este caso no hemos creado dos productos independientes.

`producto1` y `producto2` hacen referencia al mismo objeto.

Por tanto:

producto2.stock = 5
 
println(producto1.stock) 

mostrará:

5

Esto es diferente de lo que ocurre con tipos sencillos como `Int`:

var numero1 = 10
var numero2 = numero1
 
numero2 = 20
 
println(numero1) 

El resultado continúa siendo:

10

Comprender que los objetos se manejan mediante referencias será importante posteriormente cuando trabajemos con objetos, colecciones y aplicaciones Android.

Ejercicio — Gestión de vehículos

Crea una clase denominada:

Vehiculo

Cada vehículo debe almacenar:

  • Matrícula.
  • Marca.
  • Modelo.
  • Kilómetros.
  • Nivel de combustible.
  • Capacidad máxima del depósito.

Por ejemplo:

val coche = Vehiculo(
    matricula = "1234 ABC",
    marca = "Toyota",
    modelo = "Corolla",
    kilometros = 50000,
    combustible = 30.0,
    capacidadDeposito = 50.0
)

La clase debe proporcionar las siguientes funciones:

mostrarInformacion()
 
repostar(litros)
 
realizarViaje(kilometros, consumo) 

La función `repostar()` debe comprobar:

  • Que la cantidad sea positiva.
  • Que el depósito no supere su capacidad máxima.

Por ejemplo, si quedan 40 litros en un depósito de 50 y hacemos:

coche.repostar(20.0)

el depósito deberá quedar en:

50 litros

La función:

realizarViaje(kilometros, consumo)

recibirá:

  • Distancia que se quiere recorrer.
  • Consumo del vehículo en litros cada 100 km.

Por ejemplo:

coche.realizarViaje(
    kilometros = 200,
    consumo = 6.5
)

El programa deberá calcular el combustible necesario:

combustibleNecesario = kilometros * consumo / 100

Si existe combustible suficiente:

  • Restar el combustible consumido.
  • Aumentar el kilometraje del vehículo.
  • Mostrar un mensaje indicando que el viaje se ha realizado.

Si no existe combustible suficiente:

  • No modificar el kilometraje.
  • No modificar el combustible.
  • Mostrar un mensaje indicando que no existe combustible suficiente.

Además, utiliza un bloque `init` para comprobar que:

  • Los kilómetros iniciales no sean negativos.
  • El combustible no sea negativo.
  • La capacidad del depósito sea mayor que cero.

Finalmente, crea al menos tres vehículos diferentes y realiza distintas operaciones sobre ellos.

Ejercicio de ampliación

Crea una clase `Alumno` que almacene:

nombre
apellidos
edad
notas

Las notas pueden almacenarse en:

val notas = mutableListOf<Double>()

La clase deberá proporcionar las funciones:

agregarNota(nota)
 
calcularMedia()
 
estaAprobado()
 
mostrarInformacion() 

`agregarNota()` solamente deberá aceptar valores entre `0` y `10`.

`calcularMedia()` devolverá la media de las notas del alumno.

`estaAprobado()` devolverá un `Boolean` indicando si la media es igual o superior a `5`.

`mostrarInformacion()` deberá producir una salida similar a:

Alumno: Ana García
Edad: 20
Notas: 7.5, 8.0, 6.5, 9.0
Media: 7.75
Resultado: APROBADO

Crea posteriormente varios objetos `Alumno`, añade diferentes notas y muestra sus resultados.


© 2026 Fernando Valdeón

anexos/kotlin2.txt · Última modificación: por fernando