Laboratorio de Software
Taller de Kotlin - Segunda Parte
Cursada 2025
Taller de Kotlin - Segunda Parte
Cursada 2025
1 - Funciones
En Kotlin las functiones son declaradas usando la palabra clave fun.
La función sum() contiene dos parámetros Int y un tipo de retorno Int.
Las funciones que contienen una sola expresión se pueden crear con una sintáxis abreviada omitiendo las llaves {}. El tipo de retorno se infiere.
También es posible definir funciones que no devuelven valores. ¿Qué representa el tipo de retorno Unit? ¿Tiene algún equivalente en JAVA? ¿Qué retorna la función main()?
¿Qué valores toman los parámetros facultad y edad en las invocaciones a printStudent ()?
A los parámetros de las funciones se les puede asignar valores predeterminados o por defecto. Estos valores pueden utilizarse en la invocación a las funciones omitiendo los valores de los parámetros correspondientes.
Dale play al siguiente código y analizá el comportamiento.
La función sucesion() construye un String a partir de un número entero.
¿Qué observas sobre la forma de invocar a la función?
Intenta asignarle un valor por defecto al parámetro de sucesion(), por ejemplo el valor 10.
¿Es posible agregar otro parámetro, por ejemplo otro entero? .
¿La función sucesion() incorpora una funcionalidad nueva a los Strings? ¿Extiende la clase String?
¿Cómo harías en Java para lograr un comportamiento similar?
FUNCIONES DECLARADAS CON MODIFICADOR INFIX
Las funciones declaradas con el modificador infix pueden invocarse utilizando la notación infija que consiste en omitir el punto y los paréntesis. La forma tradicional o no-infija de invocación a una función es con el punto (.), por ejemplo: "Strings de 1 a 10:".sucesion(10).
Las funciones infijas cumplen los siguientes requisitos:
Pueden ser funciones miembro de clases o de interfaces o extensiones. Las funciones que son extensiones permiten extender la funcionalidad de una clase o interface sin usar herencia. De esta manera es posible escribir nuevas funciones para una clase o interface de una librería de terceros que no se puede modificar.
Deben tener un único parámetro.
El parámetro de una función infija no acepta un número variable de argumentos y no puede contener un valor por defecto.
Tarea:
Invoca a la función sucesion() de esta manera: "Strings de 1 a 10:" sucesion 10 + 5. ¿Se obtiene el resultado esperado? ¿Qué podes decir acerca de la prioridad entre funciones y operadores?
ÁMBITOS DE DEFINICIÓN DE LAS FUNCIONES
Funciones de nivel superior
Son funciones definidas fuera de cualquier clase, objeto o interface, es decir definidas directamente a nivel de paquete.
Estas funciones realizan ciertas operaciones que pueden reutilizarse en múltiples sitios pero que no forman parte de clases, ofrecen "métodos estáticos" (en terminología Java) es decir son funciones invocadas sin instanciar objetos. Para usar dichas funciones simplemente se importan como si fuesen una clase más.
Funciones miembro de clases o interfaces
Son las funciones que se declaran dentro de una clase o interface y para invocarlas es necesario disponer de una instancia.
Funciones locales
Son funciones declaradas dentro de otras funciones. Se puede acceder a las variables declaradas en ámbitos superiores.
4. Funciones extensión
Permiten agregar funciones a cualquier clase aunque no tengamos acceso a su código fuente y sin tener que crear clases que la extiendan.
Se declaran anteponiendo el nombre de la clase para la que creamos la función: fun MiClase.funcion(...){//...}.
En el ejemplo de abajo definimos en la clase Int el método multiplicar() para multiplar el número sobre el que se hace la operación por el pasado como parámetro. Alcanza con definir la función como lo hacemos normalmente y anteponiendo al nombre de la función el nombre de la clase.
Mediante el objeto this tenemos acceso al objeto sobre el que invocamos a la función y por lo tanto a sus propiedades y funciones. En términos prácticos es equivalente a crear la función dentro de la clase Int.
Tarea: Intentá agregar la función extensión div() a la clase Int con la siguiente firma: fun Int.div(numero: Int): String
2 - Expresiones Lambda en Kotlin
Son similares a las que se utilizan en Java. Una función sin nombre es llamada función anónima y las expresiones lambda son funciones anónimas.
En el ejemplo siguiente, la función imprimo() es una función lambda que realiza una salida por consola y no retorna ningún valor. En el ejemplo se muestran dos maneras distintas de invocar la expresión lambda.
Analizá el siguiente código e identifica para cada una de las expresiones lambda, suma1 y suma 2:
¿Cuáles son los parámetros y de qué tipo?
¿Cuál es el tipo de retorno?
¿El tipo de retorno de suma3 es el mismo que el de suma2?
La sintaxis de las expresiones Lambda es la siguiente: val nombre_lambda_expresion : Tipo_de_dato = { Lista_argumentos -> codigo }
Una expresión lambda siempre está rodeada de {}.
Las declaraciones de parámetros en la forma sintáctica van entre llaves y tienen anotaciones de tipo opcionales.
El cuerpo va tras el ->.
Si el tipo de retorno inferido de la expresión lambda no es vacío, la última instrucción dentro del cuerpo da el tipo de retorno.
En Kotlin, las expresiones lambda contienen secciones opcionales excepto el código del cuerpo que es obligatorio.
En el ejemplo de suma2 se omiten todas las partes opcionales.
3 - Manejo de Excepciones
En Kotlin las excepciones son unchecked. A diferencia de Java, el compilador no verifica que la excepción sea capturada y recuperado o re-disparada.
Todas las excepciones son subclases de Throwable.
De manera similar a Java, para disparar una excepción se usa la claúsula throw y para capturarla las claúsulas try-catch.
La sintaxis del bloque try-catch
try {
// código que puede disparar excepciones
} catch (e: ExceptionName) {
// captura la excepción y la maneja
}
Las excepciones más comunes en Kotlin son:
NullPointerException: se levanta si quiere acceder a una propiedad o invocar un metodo sobre una referencia null.
ArithmeticException: se levanta cuando se realiza una operación aritmética inválida. Ej: división por cero.
SecurityException: se utiliza para indicar una violación de seguridad.
ArrayIndexOutOfBoundException: se levanta cuando se accede a un índice inválido al trabajar con arreglos.
Probá el siguiente ejemplo: ¿Qué diferencia observas con el manejo de excepciones de Java?
4- Colecciones
Los tipos de colecciones usados en Kotlin son: Listas, Mapas y Conjuntos.
El paquete kotlin.collections (que se importa de manera automática) proporciona un conjunto completo de herramientas (interfaces, clases y funciones) para manejar colecciones.
Las colecciones se pueden agrupar en dos grandes categorías:
Inmutables: son de solo lectura, con operaciones para acceder a los elementos de la colección. Ejemplos:List, Set y Map.
Mutables: son modificables con operaciones de escritura como agregar, eliminar y actualizar sus elementos. Ejemplos: MutableList, MutableSet y MutableMap.
Ejemplo: ¿Se podría agregar un elemento a la lista?
Ejemplo: Teniendo en cuenta que numeros se declaró como val. ¿Se podría agregar el elemento "cinco" a la lista?
Probá el siguiente ejemplo. ¿Qué sucede con la cantidad de elementos al convertir la lista en un conjunto? ¿Por qué?
¿Qué hace el siguiente ejemplo?
Kotlin ofrece muchas funciones para procesar y transformar colecciones, entre ellas, forEach, map, filter y sorted.
Probá el siguiente ejemplo. Analiza el uso de las funciones filter y forEach. ¿Qué notás de diferente en su invocación?
Probá el siguiente ejemplo. ¿La función map funciona de la manera esperada? ¿Por qué?
Probá y analizá el siguiente ejemplo. Defina el mismo comparador usando una expresión Lambda.
En vez de usar la funcin sortedWith(), podría usarse sortedBy(). ¿Cómo se invoca? ¿Qué relación tiene con sortedWith()? Investigala y comentanos.
5 - Clases anidadas y clases internas
Clases anidadas
Una clase anidada es una clase definida dentro del espacio de nombres de una clase de nivel superior. La relación entre la clase anidada y la que la contiene, es una relación entre clases.
AeropuertoPrivado es un clase anidada privada, esto significa que es completamente invisible afuera de la clase Aeropuerto, por lo tanto no es posible crear objetos AeropuertoPrivado afuera de la clase Aeropuerto. Si una función retorna un objeto AeropuertoPrivado como lo hace la función avionPrivado(), el tipo de retorno es upcasteado a un tipo público (en este caso al tipo Aeropuerto) y no puede downcasteado a un tipo privado.
Los tipos enumerativos son clases por lo tanto pueden anidarse en otras clases. Sin embargo, no pueden anidarse dentro de funciones (locales). Las interfaces pueden anidar tipos enumerativos.
Enumerativos anidados
Clases internas
En las clases internas se establece una relación entre objetos: el objeto de la clase interna mantiene una referencia al de la clase de nivel superior.
En el siguiente ejemplo el objeto Room tiene acceso a todo el estado del objeto Hotel: la propiedad recepcion es usada en la función callRecepction(), definida en la clase Room, como si fuese propia.
Clases internas locales y anónimas
Las clases internas locales son clases definidas adentro de funciones.
Las clases internas locales también pueden definirse anónimamente usando la expresión Object o la conversión SAM.
Las clases internas locales tienen acceso tanto a las variables locales de la función como a los elementos definidos en la clase de nivel superior. En el siguiente ejemplo las clases locales internas y anónimas tienen acceso a las variables say, emit, squeak y home().