Cómo crear y usar funciones en Kotlin
Índice de contenido
- ⚙️ Parámetros y Argumentos con Nombre
- Retorno
- El tipo de retorno “Unit”
- Retornos Múltiples
- Ejemplo: Validación de Registro
- Esquema Tradicional vs. Expresión Única
- Valores por Defecto (Parámetros Opcionales)
- ⚡ Funciones de Expresión Única
- Funciones Lambda
- Las funciones de extensión en Kotlin
- Tipos nulos
No explicaré demasiado porque el concepto es universal en cualquier lenguaje: una función es una pieza de código reutilizable que realiza una tarea específica.
Lo que varía aquí es la palabra clave para definirlas. Mientras que en JavaScript se usa function, en Kotlin (por ejemplo) se utiliza fun. Su estructura básica consiste en la palabra reservada, el nombre de la función, los parámetros entre paréntesis y el cuerpo encerrado entre llaves {}.
Anteriormente vimos como usar los condicionales en Kotlin: if y when.
⚙️ Parámetros y Argumentos con Nombre
Los parámetros se separan por comas, como en el 99% de los lenguajes. Una característica muy útil es el uso de argumentos con nombre.
Esto permite invocar una función especificando el nombre del parámetro. Es extremadamente útil cuando las funciones tienen muchos argumentos o cuando no es obvio el orden de los mismos. Además, al usar nombres, puedes invertir el orden de los parámetros al llamarla sin que afecte el resultado, ya que el compilador sabe exactamente a qué variable te refieres.
fun sum(x: Int, y: Int): Int {
return x + y
}
fun main() {
println(sum(1, 2))
// 3
}Retorno
El uso de return es fundamental cuando queremos que la función realice un cálculo y nos devuelva el resultado. Por ejemplo, en una calculadora, retornaríamos el valor de una suma. Si la función solo imprime un "Hola Mundo", el retorno es opcional, ya que la tarea principal es la impresión por pantalla.
fun hello() {
return println("Hello, world!")
}
fun main() {
hello()
// Hello, world!
}El tipo de retorno “Unit”
Por defecto, si una función no devuelve nada, lo que retorna es un tipo Unit. Sin embargo, como ocurre en cualquier lenguaje, puedes manejar múltiples puntos de retorno dentro de una misma función.
Retornos Múltiples
Esto es muy útil, por ejemplo, en las validaciones de datos. Si vas a devolver un valor basado en una condición, puedes usar la sentencia return para finalizar la ejecución en cuanto se cumpla una regla.
Ejemplo: Validación de Registro
Imagina una función que recibe un username y un email. La lógica funcionaría así:
- Validación de reglas: Primero validas el username. Si no cumple con las reglas (por ejemplo, es muy corto), retornas inmediatamente un mensaje diciendo: "Este username es inválido".
- Validación de existencia: Si pasa las reglas, pero el nombre ya está registrado en la base de datos, retornas: "El nombre de usuario ya está tomado".
- Validación de formato de Email: Una vez validado el usuario, pasas al correo. Si el usuario ingresa algo como "patata" (sin formato de correo), retornas: "El email no es válido".
- Validación de duplicidad de Email: Si el formato es correcto, como patata@correo.com, pero ya existe en el sistema, retornas: "El email ya ha sido tomado".
De esta manera, puedes tener múltiples retornos en tu función basados en lo que esté sucediendo en cada etapa del proceso.
// A list of registered usernames
val registeredUsernames = mutableListOf("john_doe", "jane_smith")
// A list of registered emails
val registeredEmails = mutableListOf("john@example.com", "jane@example.com")
fun registerUser(username: String, email: String): String {
// Early return if the username is already taken
if (username in registeredUsernames) {
return "Username already taken. Please choose a different username."
}
// Early return if the email is already registered
if (email in registeredEmails) {
return "Email already registered. Please use a different email."
}
// Proceed with the registration if the username and email are not taken
registeredUsernames.add(username)
registeredEmails.add(email)
return "User registered successfully: $username"
}
fun main() {
println(registerUser("john_doe", "newjohn@example.com"))
// Username already taken. Please choose a different username.
println(registerUser("new_user", "newuser@example.com"))
// User registered successfully: new_user
}Esquema Tradicional vs. Expresión Única
Es importante destacar que, cuando manejas esta lógica de validaciones con múltiples condiciones (if/else), debes regresar al esquema tradicional de funciones (usando llaves {} y return explícitos).
No podrías usar el formato de "función de una sola línea" o expresión única, ya que ese formato está diseñado para realizar una operación directa y terminar, mientras que aquí necesitas evaluar diferentes escenarios antes de dar una respuesta final.
Valores por Defecto (Parámetros Opcionales)
Al igual que en lenguajes como PHP o Python, podemos asignar valores por defecto. Esto es ideal para parámetros que casi siempre son iguales.
fun printMessageWithPrefix(message: String, prefix: String = "Info") {
println("[$prefix] $message")
}
fun main() {
// Function called with both parameters
printMessageWithPrefix("Hello", "Log")
// [Log] Hello
// Function called only with message parameter
printMessageWithPrefix("Hello")
// [Info] Hello
printMessageWithPrefix(prefix = "Log", message = "Hello")
// [Log] Hello
}Ejemplo: Si tienes una plataforma de pagos y la mayoría usa PayPal, puedes definirlo por defecto. Así, si el usuario no especifica el método, la función asume automáticamente PayPal. Esto convierte al parámetro en opcional.
⚡ Funciones de Expresión Única
Si una función solo realiza una operación y retorna un valor, podemos simplificarla eliminando las llaves y el return, utilizando directamente el signo igual =. Esto ahorra líneas de código y hace que la función sea mucho más legible.
// Función tradicional
fun doble(x: Int): Int { return x * 2 }
// Función de expresión única
fun doble(x: Int) = x * 2Funciones Lambda
Las funciones Lambda son aquellas que podemos asociar directamente a variables. Son muy útiles para modularizar tareas específicas que solo se ejecutarán dentro de un contexto particular.
Se suelen conocer también como "funciones de flecha" por su sintaxis:
- Definimos el parámetro.
- Colocamos la flecha ->.
- Escribimos la operación a realizar.
fun uppercaseString(text: String): String {
return text.uppercase()
}
fun main() {
println(uppercaseString("hello"))
// HELLO
}Las funciones de extensión en Kotlin
Las funciones de extensión son una poderosa herramienta que nos ofrece Kotlin al momento de desarrollar aplicaciones en Android.
Una manera de verlo es como si pudiéramos extender en base a funciones clases definidas ya sea por nosotros, de terceros o provistas por Kotlin de una manera rápida y sin complicaciones.
Por ejemplo, si quisiéramos extender el tipo de dato Int con una función que nos indica si es un número par:
fun Int.isEven(): Boolean = (this % 2 == 0)Con esta función estamos expresando en una función que extiende de los tipos de datos enteros que verifique si un número es par o no; para emplearlo podemos hacer lo siguiente:
println(10.isEven())La respuesta del compilador sería true.
Como indicamos en un inicio, también podemos emplear las funciones de extensión en otros tipos de estructuras como clase; por ejemplo, tomando la clase anterior Persona:
class Persona(nombre: String, apellido: String, edad: Int) {
var nombre: String = ""
var apellido: String = ""
var edad: Int = 0
init {
this.nombre = nombre
this.apellido = apellido
this.edad = edad
}
}Podemos hacer lo siguiente:
fun Persona.esTerceraEdad(): Boolean = edad >= 60También la podemos simplificar como:
fun Persona.esTerceraEdad() = edad >= 60
var persona = Persona("Andrés"," Cruz",27)
println(persona.esTerceraEdad())Para este ejemplo, en la que la edad es menor a 60 años retorna false; si por el contrario, creamos una persona como:
var persona = Persona("Andrés"," Cruz",80)La respuesta sería true.
Tipos nulos
Note que en base a los ejemplo que estamos viendo anteriormente, nada nos impide que empleemos funciones de extensión con variables nulas en un momento dado, esto podría ser un problema pero lo podemos solventar fácilmente realizando una comprobación previa e indicando el operador en cuestión luego de indicar el nombre de la clase en la función de expresión:
fun Int?.isEven(): Boolean {
if (this != NULL) return this % 2 == 0
return false;
}
println(NULL.isEven())Protección de Nulos (Null Safety): Si queremos que nuestra función de extensión sea segura ante valores nulos, añadimos el signo de interrogación ? al tipo de dato (ej. Int?). Esto nos permite realizar verificaciones internas antes de operar, evitando que la aplicación se rompa (NullPointerException).
El código anterior, nos devolvería false; veamos otro ejemplo sobrescribiendo el método toString():
fun Any?.toString(): String {
if (this == NULL) return "nada"
return toString()
}
println(NULL.toString())Y esto nos devuelve el texto nada.
Ahora, aprende a usar las clases en Kotlin.
Acepto recibir anuncios de interes sobre este Blog.
Guía completa sobre funciones en Kotlin: aprende sintaxis, parámetros nombrados, lambdas, funciones de extensión y manejo de nulos con ejemplos prácticos.