Kotlin Conceptos Básicos

En este artículo analizaremos los conceptos básicos de kotlin, uno de los lenguajes de programación con mayor crecimiento en los últimos años. Para comenzar a ver las maravillas de este gran lenguaje, veamos los puntos a descubrir:

Luis David Orellana
14 min readNov 7, 2021

--

  1. Variables
  2. Modificadores de visibilidad
  3. Getters y Setters
  4. Null Safety
  5. Condicionales y Bucles
  6. Constructores
  7. Type Alias
  8. Object y Companion Object
  9. Interface
  10. Data Class
  11. Enum Class

Estas list@, comencemos…

1️⃣ Variables

var

  • Variable mutable, es decir que su valor se puede reasignar varias veces y se determina en tiempo de ejecución.
  • El tipo de dato puede ser opcional, siempre y cuando su valor no sea nulo.
fun main() {
// Declaración de var
var tienda: String = "Una libra de arroz, por favor"
tienda = "Me puede adicionar dos libras de arroz, por favor"
println(tienda) // Me puede adicionar dos libras de arroz, por
favor
// Tipo de dato Boolean
var declaracion = true
println(declaracion) // true
}

Los tipos de datos en kotlin son: Byte, Short, Int, Long, Float, Double, String, Char, Boolean

val

  • Variable de solo lectura e inmutable. Su valor no puede ser reasignado en un futuro y se determina en tiempo de ejecución.
  • El tipo de dato también puede ser opcional, siempre y cuando su valor no sea nulo.
fun main() {
// Declaración de val
val hola: String = "Bienvenidos a conceptos básicos de Kotlin"
println(hola) // Bienvenidos a conceptos básicos de Kotlin
// Error: No se puede declarar un valor nulo
/* val nullDeclarado: Long = null */
}

Declaración de valores nulos lo veremos en el punto 4 (Null Safety)

const val

  • Es una variable inmutable de solo lectura, que su valor no va a hacer cambiado en un futuro. La diferencia con val, es que const val se determina en tiempo de compilación.
  • Debe ser una propiedad de nivel superior es decir global o un miembro de una declaración object o companion object
  • Una buena práctica en kotlin es definir el nombre de las variables const val en mayúsculas.
  • Siempre se debe inicializar su valor.
// Variable de nivel global (nivel superior)
const val PI: Double = 3.1416
const val LENGUAJE_OFICIAL: String = "Kotlin"
const val PRIMERA_VOCAL = 'A' // Se puede inferir el tipo de dato
fun main() {
// Declaración de const val
println("El valor de PI es: $PI") // El valor de PI es: 3.1416
println("El lenguaje oficial de android es: $LENGUAJE_OFICIAL")
// El lenguaje oficial de android es: Kotlin
println("La primera vocal es: $PRIMERA_VOCAL") // La primera
vocal es: A
}

2️⃣ Modificadores de visibilidad

Las clases, objetos, interfaces, constructores y funciones, así como las propiedades y sus setters, pueden tener modificadores de visibilidad. Los getters siempre tienen la misma visibilidad que sus propiedades. Las variables, funciones y clases locales no pueden tener modificadores de visibilidad.

  • private: Solo es visible dentro del archivo en el cual fue declarado y también es visible dentro de la clase contenedora (incluido todos sus miembros).
  • public: Su implementación esta por defecto, lo que significa que sus declaraciones serán visibles en todas partes.
  • protected: No está disponible para declaraciones de nivel superior. Es visible en la clase contenedora y subclase de la misma.
  • internal: Es visible en todas partes del mismo modulo, es decir un conjunto de archivos kotlin compilados juntos.

Puedes revisar ejemplos de los modificadores de visibilidad en mi repositorio de Github.

3️⃣ Getters y Setters

En kotlin los getters y setters se generan automáticamente según el tipo de variable que ha implementado (val o var).

  • Se pueden personalizar los getters y setters.
  • La variable val, por ser inmutable genera getters.
  • La variable var, por ser mutable genera getters y setters.
  • const val no puede generar getters personalizados.
  • La palabra reservada field, tiene acceso al valor del campo asignado.
const val GETTERS = "GETTER en tiempo de compilación" class GettersYSetters {
// Personalización Getter Y Setter
var lenguaje: String = "Kotlin"
get() {
println("Retorna el valor por defecto: $field")
return field // Palabra reservada field, tiene acceso
al valor del campo asigando.
}
set(nuevoValor) {
println("Retorna el nuevo valor asignado:
$nuevoValor")
field = nuevoValor // Se asigna el nuevo valor
}

var miTallaDeZapatos = 40 // Genera automáticamente getter
y setter
var privateSetters: String = "abc"
private set // Setter privado
// Personalización Getter
val estadoAnimo: String
get() = "Super Feliz"

val lenguajeCienciaDatos = "Python" // Solo genera getter
}
fun main() {
println(GETTERS) // GETTER en tiempo de compilación

val gettersYSetters = GettersYSetters()
gettersYSetters.lenguaje // Retorna el valor por defecto: Kotlin
gettersYSetters.lenguaje = "JS" // Retorna el nuevo valor
asignado: JS

println(gettersYSetters.miTallaDeZapatos) // 40
gettersYSetters.miTallaDeZapatos = 41
println(gettersYSetters.miTallaDeZapatos) // 41

/* gettersYSetters.privateSetters = "" */ // Error: Setter
privado, no puede ser reasignado
println(gettersYSetters.privateSetters) // abc

/* gettersYSetters.estadoAnimo = "Calmado" */ // Error: No puede
ser reasigando; val no genera setter
println(gettersYSetters.estadoAnimo) // Super Feliz
/* gettersYSetters.lenguajeCienciaDatos = "JS" */ // Error: No
puede ser reasigando; val no genera setter
println(gettersYSetters.lenguajeCienciaDatos) // Python
}

4️⃣ Null Safety

En kotlin los objetos por defecto no aceptan valores nulos, para poder asignar un valor nulo tendremos que indicar que ese objeto realmente pueda ser nulo.

Understanding Kotlin Type System (brunoaybar.com)
  • Operador Safe Call (?): En los sistemas de tipos, para poder hacer una llamada segura en valores nulos colocar el siguiente operador llamado Safe Call (?)
  • Operador Elvis (?:): Sirve para hacer una comprobación, si una condición no coincide la otra se ejecutará.
  • Operador Double Bang (!!): Con este operador se evita la necesidad de chequear nulos, si está completamente seguro de que una variable nunca será nula.
  • let: Para realizar una determinada operación solo para valores no nulos, puede utilizar el operador Safe Call (?) junto con let.
  • Existen funciones que ayudan a comprobar si hay valores nulos como: filterNotNull(), isNullOrBlank(), isNullOrEmpty(), maxOrNull(), randomOrNull() y muchas más.
fun main() {  
var a: String = "kotlin"
/* a = null */ // Error de compilación
// Si la variable no es nula, se puede omitir el operador Safe
Call (llamada segura)
println(a?.length) // 6
val b: String? = null
println(b?.length) // null

val c = b?.length ?: -1
println(c) // -1
//val d = b!!.length
//println(d) // Error: NullPointerException
val nullList: List<Int?> = listOf(1, 2, null, 4)
println(nullList.filterNotNull()) // [1,2,4]
println(nullList.elementAtOrNull(10)) // null
val letList: List<String?> = listOf("java", null, "kotlin")
for (list in letList) {
list?.let { // Con el operador Safe Call, ignora los
valores nulos.
print("$it ") // java kotlin
}
}
}

5️⃣ Condicionales y Bucles

Condicional if

  • En kotlin if es una expresión, es decir devuelve un valor.
  • if nos permite ejecutar un bloque de código. Si la condición es verdadera, ejecutará lo implementado en if y si no se cumple ejecuta otra condición o concluirá su proceso.
  • Si if se usa como expresión para devolver su valor o asignarlo a una variable, la utilización else es obligatoria.
fun main() {
// Condicional if
val mayorDeEdad = (1..100).random() // Número al azar del 1 al
100
if (mayorDeEdad >= 18) println("Es mayor de edad con
$mayorDeEdad") // Se ejecutará si esta en el rango de 18 al 100.

val mayorOMenor = if (mayorDeEdad >= 18) "Persona mayor" else
"Persona menor con $mayorDeEdad"
println(mayorOMenor)// Ejecutará cualquiera de las 2
condiciones.
if (mayorDeEdad >= 41) {
println("Tiene ganas de descansar")
} else if (mayorDeEdad in 18..40){
println("Tiene ganas de trabajar")
} else {
println("Tiene ganas de jugar")
}
}

Condicional when

  • when define una expresión condicional con varias ramas.
  • when se puede utilizar como declaración o como expresión, y si es una expresión la rama else es obligatoria, a menos que se garantice que las ramas definidas cubren todas las posibles opciones.
  • El operador in sirve para verificar si un valor está dentro del rango o en una colección.
  • El operador is permite comprobar si una variable es de un tipo asignado.
fun main() {
// Condicional when
print("Ingrese un número del 1 al 10: ")
val azar: Any? = readLine()?.toIntOrNull() // Me permite
ingresar un valor en consola.
when (azar) { // Declaración when
in 1..3 -> println("El número esta en el rango 1 al 3")
4,5,6 -> println("El número ingresado esta en los intervalos
de 4, 5 y 6")
is String? -> println("Solo esta permitido ingresar números
enteros")
in 7..10 -> println("El número esta en el rango 7 al 10")
!in 1..10 -> println("Número fuera del rango")
}
}

Bucle for

  • Es utilizado para iterar cualquier cosa que proporcione un iterador.
  • Utiliza el operador in para recorrer los datos estructurados.
  • Los datos estructurados pueden ser array, ranges, list, String, etc.
fun main() {
// Bucle for
for (i: Int in 1 until 5) print(i) // 1,2,3,4
val nombres = listOf("Armando", "David", "Cely", "Martha")
for (nombre in nombres.indices) { // Devuelve el índice de cada
elemento
if (nombre % 2 == 0) // Solo índices pares
println(nombres[nombre]) // Armando[0], Cely[2]
}
}

Bucle while y do-while

  • while comprueba la condición y si cumple ejecuta el cuerpo y a continuación vuelve a la comprobación de condición. Este bucle repetirá su cuerpo mientras la condición sea true o alguna expresión de salto sea evaluada.
fun main() {
var suma = 0
var valor = 1
// Bucle while
while (valor <= 5) {
suma += valor++
}
println("Suma total: $suma") // Suma total: 15
}
  • do-while ejecuta el cuerpo y a continuación comprueba el estado. Si está satisfecho el bucle se repite. Por lo tanto, el cuerpo se ejecuta al menos una vez, independientemente de la condición.
fun main() {
// Bucle do-while
do {
println("¿Cuál es la capital del Ecuador?")
println("a. Quito")
println("b. La Paz")
println("c. Lima")
println("Por favor ingresar: a, b o c")
print("Respuesta: ")
val opciones = readLine()!! // Ingresa valor en consola.
val comprobación = opciones != "a"
if (comprobación) {
println("Es incorrecto, vuelve a intentarlo! :(")
} else {
println("Es correcto, felicitaciones! :)")
}
} while (comprobación) // Ejecuta el cuerpo y hace la
comprobación. Si es incorrecto se repite el bucle, caso
contrario, termina su proceso.
}

6️⃣ Constructores

Constructor Primario o Principal

  • Forma parte del encabezado de la clase y va tras el nombre de la clase y los tipos de parámetros son opcionales.
  • Si el constructor principal no tiene anotaciones ni modificadores de visibilidad, se puede omitir la palabra clave constructor, caso contrario se debe ingresar.
  • Las variables val o var en el constructor primario, crea propiedades automáticas que permite ser referenciadas, en un futuro por medio de su tipo de nombre.
  • Bloque init es un inicializador adicional que se llama después del constructor principal, para poder contener código y además puede tener uno o varios init.

Constructor Secundario

  • Puede declararse un constructor secundario, con la palabra clave constructor, en el interior de la clase.
  • El cuerpo del constructor secundario se llama después del bloque init.
  • Si la clase tiene un constructor primario, cada constructor secundario debe delegar en el constructor primario, ya sea directa o indirectamente a través de otro constructor secundario. La delegación a otro constructor de la misma clase se realiza utilizando la palabra clave: this
  • En los parámetros del constructor secundario, no está permitido ingresar variables val y var.

Implementación de constructores:

// Constructor Primario
class Datos internal constructor(_nombre: String = "Jorge", _apellido: String) {
val nombre = _nombre.uppercase()
val apellido: String = _apellido.uppercase()
private var edad: Int = 25
// Bloque init
init {
print("Los datos son los siguientes: ") // 1 Ejecutarse
}
// Constructor secundario
constructor(nombre: String, apellido: String, edad: Int) :
this(nombre, apellido) {
this.edad = edad
}
init {
print("La edad es: ${edad.inc()}, ") // 2 Ejecutarse
}
}
fun main() {
val datos = Datos("Luis", "Orellana")
// 3 Ejecutarse
println("Nombre: ${datos.nombre} y Apellido: ${datos.apellido}")
// Los datos son los siguientes: La edad es: 26, Nombre: LUIS y
Apellido: ORELLANA
}

7️⃣ Type Alias

  • Proporcionan nombres alternativos para los tipos existentes. Si el nombre del tipo es demasiado largo, puede introducir un nombre más corto diferente y usar el nuevo en su lugar.
  • Los typealias se declaran desde el nivel superior.
  • Los typealias no introducen nuevos tipos. Son equivalentes a los tipos subyacentes correspondientes.
typealias Usuario = Triple<String, String, Int>
typealias Direccion = Pair<Double, Double>
typealias Resultado = (Int, Int) -> Int
fun usuario(primero: String, segundo: String, tercero: Numericos): Usuario {
return Triple(primero, segundo, tercero)
}
fun mapaDireccion(latitud: Latitud, longitud: Longitud) : Direccion {
return Pair(latitud, longitud)
}
fun main() {
val usuario = usuario("Fred", "Gaus", 10)
println(usuario.first) // Fred
println(usuario.second) // Gaus
println(usuario.third) // 1
val mapaDireccion = mapaDireccion(10450.25, 12466.174)
println("Latitud: ${mapaDireccion.first}, Longitud:
${mapaDireccion.second}") // Latitud: 10450.25, Longitud:
12466.174
val suma: Resultado = { x: Numericos, y: Numericos ->
x + y
}
println("La suma es: ${suma(578,125)}") // La suma es: 703
}

8️⃣ Object y Companion Object

Object

  • Un object es un tipo de dato con una única instancia estática (Patrón Singleton) y no posee constructores.
  • Los object pueden ser expresiones de objeto o declaraciones de objeto y se utilizan con la palabra clave object.
  • Las declaraciones object no pueden ser locales (es decir, no se pueden anidar directamente dentro de una función), pero se pueden anidar en otras declaraciones object o clases no internas.
  • Las declaraciones object se inicializan, cuando se accede a ellas por primera vez y siempre tiene un nombre después de la palabra clave.
  • Las expresiones object se ejecutan (e inicializan) inmediatamente, donde se utilizan y no puede enlazar un nombre después de colocar la palabra clave, pero si es posible heredar de las clases existentes o implementar interface.
  • Las expresiones object crean objetos de clases anónimas, es decir, clases que no se declaran explícitamente con la declaración. Tales clases son útiles para un solo uso.
// Declaración Object
object CuerpoHumano {
const val numeroHuesos = 206
fun print(nombre: String = "Armando"): String {
return "Hola, mi nombre es $nombre y tengo $numeroHuesos
huesos."
}
}
interface CargoDeEmpleo {
fun cargoTrabajo(cargoEmpleo: String): String
}
fun main {
// Declaración object
val cuerpoHumano = CuerpoHumano
println(cuerpoHumano.print()) // Hola, mi nombre es Armando y
tengo 206 huesos.

val datos = object : CargoDeEmpleo { // Expresión Object
var nombre: String = "Luis"
var apellido: String = "Orellana"
override fun toString(): String {
return "Mi nombre es: $nombre y mi apellido es:
$apellido."
}
override fun cargoTrabajo(cargoEmpleo: String): String {
return "Mi cargo de trabajo actualmente es:
$cargoEmpleo."
}
}
println(datos) // Mi nombre es: Luis y mi apellido es: Orellana.
println(datos.cargoTrabajo("Desarrollador Android")) // Mi cargo
de trabajo actualmente es: Desarrollador Android.
}

Companion Object

  • Toda clase puede tener un companion object, que es un objeto que es común a todas las instancias de esa clase.
  • En los companion object se le puede asignar un nombre, pero no necesario, ya que viene por defecto el nombre Companion y es posible heredar de las clases existentes o implementar interface.
  • Los miembros de la clase pueden acceder a los miembros privados del companion object correspondiente.
  • Un companion object se inicializa cuando se carga la clase correspondiente.
// Implementación companion object
class EmpresaDeCocina {
companion object : CargoDeEmpleo { // Se puede omitir el nombre
del companion object, en cuyo caso se utilizará el nombre:
Companion
private const val NOMBRE_LOCAL = "Restaurante Mariela"
override fun cargoTrabajo(cargoEmpleo: String): String {
return "Trabajo en $NOMBRE_LOCAL, Mi cargo en el trabajo
es de: $cargoEmpleo."
}
}
}
fun main() {
// companion object
val empresaDeCocina =
EmpresaDeCocina.Companion.cargoTrabajo("Concinero") // Se puede
omitir el nombre Companion
println(empresaDeCocina) // Trabajo en Restaurante Mariela, Mi
cargo en el trabajo es de: Concinero.
}

9️⃣ Interface

  • Las interfaces en kotlin pueden contener declaraciones de métodos abstractos, así como implementaciones de métodos.
  • Por definición las interfaces son abstractas y se sobrescriben con el modificador override si sus propiedades o métodos no son regulares.
  • Tiene similitud con las abstract class, solo que las interfaces no permiten almacenar estados, no permite constructores.
  • Puede contener métodos abstractos y métodos regulares con implementación.
  • Puede contener propiedades abstractas y propiedades regulares, pero sin campos de respaldo.
  • Una clase u objeto puede implementar una o más interfaces. Además, una interface puede derivar de otra interface.
  • Si existen múltiples interfaces que tienen el mismo nombre del método, puede sobrescribirlo en la clase correspondiente implementando la siguiente palabra clave para poder llamarlo, super<Nombre de interface> seguido del nombre del cual se sobrescribió.
interface Figura {
fun calcularSuperficie(): Int // Método abstracto
fun calcularPerimetro(): Int // Método abstracto
fun tituloResultado() { // Método Regular
println("El resultado final")
}
}
class Cuadrado(private val lado: Int): Figura {
override fun calcularSuperficie() = lado * lado
override fun calcularPerimetro() = lado * 4
}
class Rectangulo(private val ladoMayor:Int, private val ladoMenor: Int): Figura {
override fun calcularSuperficie() = ladoMayor * ladoMenor
override fun calcularPerimetro() = (ladoMayor * 2) + (ladoMenor
* 2)
}
fun main() {
val cuadrado = Cuadrado(5)
cuadrado.tituloResultado() // El resultado final
println("Perimetro del cuadrado:
${cuadrado.calcularPerimetro()}") // Perimetro del cuadrado: 20
println("Superficie del cuadrado:
${cuadrado.calcularSuperficie()}") // Superficie del cuadrado:
25
val rectangulo = Rectangulo(6, 4)
rectangulo.tituloResultado() // El resultado final
println("Perimetro del rectangulo:
${rectangulo.calcularPerimetro()}") // Perimetro del rectangulo:
20
println("Superficie del rectangulo:
${rectangulo.calcularSuperficie()}") // Superficie del
rectangulo: 24
}

🔟 Data Class

  • Útil para mantener datos y nos evita boilerplate.
  • El constructor principal debe tener al menos un parámetro y deben marcarse como val o var.
  • Para excluir propiedades, puede declararlas en el interior del cuerpo de la data class.
  • Las data class no pueden ser abstractas, abiertas, selladas o internas. Pero si puede extender otras clases e interfaces.

El compilador deriva automáticamente las siguientes funciones:

  • equals(): Devuelve true si dos objetos tienen el mismo contenido y funciona de manera similar a “==”.
  • hashCode(): Devuelve un valor de código hash para el objeto.
  • componentN(): Permite hacer referencias a las propiedades del constructor, para ser implementados en su orden de declaración.
  • toString(): Devuelve un String de todos los parámetros definidos en el constructor principal.
  • copy(): Proporciona una copia del objeto de todos los parámetros definidos en el constructor, para poder alterar algunas o todas las propiedades.
data class Libros(
val nombre: String,
val categoria: String,
val precio: Double) {
var numeroPublicaciones: Int = 0
}

fun main() {
val libros = Libros("Harry Potter", "Fantasía", 25.5)
println(libros.toString()) // Libros(nombre=Harry Potter,
categoria=Fantasía, precio=25.5)

val libroNuevo = libros.copy(nombre = "Narnia", precio = 35.75)
println(libroNuevo) // Libros(nombre=Narnia,
categoria=Fantasía, precio=35.75)
val hashHarryPotter = libros.hashCode()
println(hashHarryPotter) // -2136733403
val hashNarnia = libroNuevo.hashCode()
println(hashNarnia) // 676881312
val equalsLibro = hashHarryPotter == hashNarnia
println(equalsLibro) // false
val componentLibro = libros.component1()
println(componentLibro) // Harry Potter

val (nombre, categoria, precio) = libros
println("Nombre del libro: $nombre, categoría: $categoria,
precio: $precio") // Nombre del libro: Harry Potter, categoría:
Fantasía, precio: 25.5
libros.numeroPublicaciones = 7
println("${libros.nombre} tiene ${libros.numeroPublicaciones}
novelas infantiles") // Harry Potter tiene 7 novelas
infantiles
}

1️⃣1️⃣ Enum Class

  • Si ingresa propiedades en el constructor primario, las constantes enum serán inicializadas con la instancia del tipo de dato requerido.
  • Si la enum class define algún miembro, separe las definiciones de constantes de las definiciones de miembro con un punto y coma (;), porque establece la separación de otros miembros existentes.
  • Cada constante enum es un objeto y está separada por comas.
  • Es posible declarar propiedades y métodos en el interior de las enum class.
  • Para interpretar los elementos de un enum, podemos usarla con la expresión when.
  • Los enum class pueden implementar interface, pero no puede derivar de una clase.

Posee propiedades y métodos adicionales como:

  • ordinal: Propiedad que almacena el índice del valor de la constante, la primera constante comienza con el valor cero.
  • name: Esta propiedad almacena el nombre de la constante.
  • values(): Este método devuelve una lista de todas las constantes definidas dentro del enum.
  • valuesOf(): Este método devuelve la constante definida en el enum class. Si la constante no está presente en el enum class, lanzara una excepción (IllegalArgumentException).
enum class Operacion(val signoOperacion: String) {
SUMA("+"), RESTA("-"),
MULTIPLICACION("*"), DIVISION("/")
}
class Calcular(
private val valorUno: Int,
private val valorDos: Int,
private val operacion: Operacion
) {
fun calcularOperacion(): Int = when (operacion) {
Operacion.SUMA -> valorUno + valorDos
Operacion.RESTA -> valorUno - valorDos
Operacion.MULTIPLICACION -> valorUno * valorDos
Operacion.DIVISION -> valorUno / valorDos
}
}
fun main() {
val calculo1 = Calcular(10, 10, Operacion.SUMA)
println("El resultado de la suma es:
${calculo1.calcularOperacion()}") // El resultado de la suma es:
20
val calculo2 = Calcular(10, 10, Operacion.RESTA)
println("El resultado de la resta es:
${calculo2.calcularOperacion()}") // El resultado de la resta
es: 0
val calculo3 = Calcular(10, 10, Operacion.MULTIPLICACION)
println("El resultado de la multiplicación es:
${calculo3.calcularOperacion()}") // El resultado de la
multiplicación es: 100
val calculo4 = Calcular(10, 10, Operacion.DIVISION)
println("El resultado de la división es:
${calculo4.calcularOperacion()}") // El resultado de la división
es: 1
}

¡Muchas Gracias por leer! 🤗💙

Puedes consultar el código de los ejercicios implementados en cada punto y más, en mi repositorio de GITHUB.

Para más, Documentación oficial de KOTLIN.

Regálame un 👏 si te gusto 🤗 y siéntete libre de dejar tus comentarios para analizarlos juntos. Nos vemos en la próxima ✌.

--

--