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:
- Variables
- Modificadores de visibilidad
- Getters y Setters
- Null Safety
- Condicionales y Bucles
- Constructores
- Type Alias
- Object y Companion Object
- Interface
- Data Class
- 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 queconst 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
ocompanion 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 datofun 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
ovar
).
- 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.
- Operador
Safe Call (?):
En los sistemas de tipos, para poder hacer una llamada segura en valores nulos colocar el siguiente operador llamadoSafe 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 operadorSafe Call (?)
junto conlet
.- 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 enif
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ónelse
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 ramaelse
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 seatrue
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
ovar
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 variosinit
.
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
yvar
.
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) -> Intfun 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ónSingleton
) y no posee constructores. - Los
object
pueden ser expresiones de objeto o declaraciones de objeto y se utilizan con la palabra claveobject
. - 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 declaracionesobject
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 implementarinterface
. - 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 nombreCompanion
y es posible heredar de las clases existentes o implementarinterface
. - 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 modificadoroverride
si sus propiedades o métodos no son regulares. - Tiene similitud con las
abstract class
, solo que lasinterfaces
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, unainterface
puede derivar de otrainterface
. - 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
ovar
. - 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():
Devuelvetrue
si dos objetos tienen el mismo contenido y funciona de manera similar a “==”.hashCode():
Devuelve un valor de códigohash
para el objeto.componentN():
Permite hacer referencias a las propiedades del constructor, para ser implementados en su orden de declaración.toString():
Devuelve unString
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ónwhen
. - Los
enum class
pueden implementarinterface
, 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 delenum
.valuesOf():
Este método devuelve la constante definida en elenum class
. Si la constante no está presente en elenum 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
}