Laboratorio de Software
Taller de Kotlin - Primera Parte
Cursada 2025
Taller de Kotlin - Primera Parte
Cursada 2025
1- Clases, constructores primarios, propiedades y la función main()
¿Cómo declaro una clase en Kotlin? ¿Cómo creo un objeto?
¿Cuáles son las propiedades de un Rectangulo?
¿Cuál es la diferencia entre las propiedades ancho, alto y perímetro? ¿Cuál es el constructor y qué parámetros tiene? ¿Cuántas variables de instancia tiene Rectangulo?
Copiá el ejemplo anterior en https://play.kotlinlang.org/ y crea un rectángulo nuevo en la variable rect ¿Es posible? ¿Cómo se puede hacer?
¿Se pueden crear objetos de otras clases en el mismo main()? ¿Observas alguna diferencia entre el método main() de Java y la función main() de Kotlin?
Copiá el siguiente ejemplo en https://play.kotlinlang.org/ y ejecutálo pasando distintos parámetros en la función main().
Declaramos el objeto casa como val: ¿es posible modificar su estado? ¿por qué?
Declaramos al objeto sofa como var: ¿es posible modificar su estado? ¿por qué?
¿Podrían inicializarse los objetos de distintas maneras? ¿Cómo se puede hacer?
SÍNTESIS CONSTRUCTORES PRIMARIOS
Los constructores primarios forman parte del encabezado de la clase y se ubican después del nombre de la clase y de los parámetros opcionales (anotaciones y modificadores de visibilidad). Por defecto son públicos.
Se declaran con la palabra clave constructor:
class Persona constructor(nombre: String) { /*...*/ }
Si el constructor primario no tiene anotaciones ni modificadores de visibilidad, puede omitirse la palabra clave constructor.
class Persona (nombre: String) { /*...*/ }
El constructor primario no puede contener código de inicialización. Si es necesario definirlo, se ubica en bloques de inicialización prefijados con la palabra clave init.
Durante la inicialización de una instancia, los bloques de inicialización se ejecutan en el mismo orden en que aparecen en el cuerpo de la clase, intercalados con la inicialización de propiedades.
Los parámetros de los constructores primarios pueden usarse en los bloques de inicialización y también para la inicialización de propiedades.
RECAPITULANDO
¿Qué diferencia hay entre declarar una variable como var o val?
var y val controlan referencias no objetos.
var: permite volver a asociar una referencia con un objeto diferente.
fun main() {
var sofa = Sofa()
println("El sofa es ${sofa.material}")
sofa= Sofa()
}
En el ejemplo anterior los objetos de tipo Sofa definidos como var no pueden cambiar su estado dado que sus propiedades (material) están definidas como val. Definir los objetos Sofa como var permite que las referencias puedan reasignarse a otros objetos (sofa=Sofa()) sin embargo el estado de los objetos no puede cambiar dado que sus propiedades son inmutables.
val: impide reasignar la referencia de un objeto.
fun main() {
val casa = Casa()
casa.sofa="de tela"
println("El sofa es ${casa.sofa}")
casa.sofa="de cuero sintético"
println("El sofa es ${casa.sofa}")
}
En el ejemplo anterior los objetos de tipo Casa defindos como val pueden cambiar su estado dado que sus propiedades (sofa) está ndefinidas como var. Por lo tanto, definir los objetos Casa como val evita que sean reasignados a otros objetos (❌ casa=Casa()), sin embargo su estado puede cambiar porque sus propiedades son mutables.
¿Es posible declarar más de un constructor? ¿Cómo?
Si la clase tiene un constructor primario, cada constructor secundario debe delegar en el constructor primario, directamente o indirectamente (a través de otro/s constructor/es secundario/s). La delegación a otro constructor de la misma clase se realiza utilizando la palabra clave this:
class Persona (val nombre: String) {
val hijes: MutableList<Persona> = mutableListOf()
constructor(nombre: String, padre: Persona) : this(nombre) {
padre.hijes.add(this)
}
}
2- Tipos anulables y no-anulables. Invocaciones seguras. Casting seguro
Dale play al código de abajo ¿Qué resultado obtuviste? ¿En JAVA funciona igual?
¿Cómo hacemos para admitir el valor null?
Copiá el ejemplo anterior en https://play.kotlinlang.org/ quitándole el operador ? a la variable b. ¿Qué sucede? ¿Qué función cumple dicho operador?
Disponer de variables no anulables (no admite null) ¿es un beneficio o no? ¿por qué?
Modifica el ejemplo anterior para que la invocación a length sobre b devuelva la longitud (si b no es nulo) o -1 si lo es.
Una forma concisa de hacer este tipo de chequeos es con el operador Elvis ?: retorna el valor de la expresión de la izquierda del operador, y el de la derecha en otro caso.
Encadenamiento de invocaciones seguras: Eva es una programadora que puede estar asignada a un proyecto (o no). A su vez, ese proyecto puede tener un líder. ¿Cómo hacemos para obtener el nombre del líder del proyecto al que está asignada Eva (si existe hay uno)?
Eva?.proyecto?.lider?.nombre
Esta cadena devuelve null si alguna de las propiedades de la cadena es null.
Tarea: dado el siguiente código en Kotlin, completálo para que imprima los elementos no nulos de la lista:
fun main() {
val listaConNulls: List<String?> = listOf("Kotlin", null)
// completar
}
Dale play al código de abajo. ¿Qué resultado obtuviste?
Modifique el ejemplo anterior reemplazando la invocación as? por as. Cuando se vuelve a ejecutar ¿Qué sucede ahora?.¿Por qué?
3-Clases y Herencia
Miembros de una clase
Constructores y bloques de inicialización
Funciones
Propiedades
Clases anidadas e internas
Declaraciones de objectos
Ejecutá el siguiente código que crea una subclase de Figura ¿Pudiste hacerlo?
Todas las clases en Kotlin tienen una superclase común, Any, que es la superclase por defecto. El equivalente a Object en Java.
class Ejemplo // Implícitamente hereda de Any
En Any se definen tres métodos: equals(), hashCode() y toString() que están disponibles para todas las subclases.
En Kotlin las clases son finales por defecto, por lo tanto no pueden crearse subclases. Para hacer que una clase sea heredable, hay que declararla open. Modificá el código anterior para que funcione. ¿Por qué pensás que se tomó esta decisión de diseño en el lenguaje?
También las funciones de una clase por defecto son finales, inclusive las declaradas en clases open. Si queremos que una clase open (heredable) contenga funciones que se sobreescriban en sus subclases debemos declararlas open también.
Dale play al siguiente código. ¿Alcanza con indicar que una función es open para sobrescribirla? ¿Qué ocurre si quitamos la palabra clave override?
Kotlin requiere modificadores explícitos para indicar que un miembro puede ser sobreescrito (open) y para indicar que se sobreescribe (override).
Un miembro declarado con override es en sí mismo abierto, por lo que puede ser sobrescrito en subclases. Si se quiere prohibir la reescritura, es necesario declararlo final.
class RectanguloRustico (var ancho: Double, var alto: Double): Rectangulo(ancho, alto) {
override final fun dibujar () { println("dibujar() de RectanguloRustico") }
}
Si la clase derivada tiene un constructor primario, la clase base debe ser inicializada con dicho constructor.
open class Base (val p: Int)
class Derivada (val p: Int): Base(p)
4-Clases Abstractas e Intefaces
Una clase puede declararse abstracta junto con algunos o todos sus miembros. Por defecto las clases y funciones abstractas son open.
Las interfaces en Kotlin, en forma similar a Java, pueden contener funciones abstractas y funciones con implementación (métodos de default). La diferencia con clases abstractas es que las interfaces no contienen estado.
Las interfaces en Kotlin pueden declarar propiedades abstractas o implementadas con métodos get().
¿Es posible sobreescribir la función motor()? ¿Qué ocurre si motor() se hereda de más de un supertipo?
Dado el siguiente código en Kotlin:
¿Cuál es la propiedad abstracta y cuál es la implementada? ¿Qué valores tienen?
Complete con las clases Avion y Barco que implementan la inteface Vehiculo.
De la misma manera que en Java, es posible combinar interfaces con clases y sacar provecho de ambas, como se muestra en el siguiente código:
Tarea: modifica el ejemplo anterior incorporando una nueva interface llamada Levitador que sea subinterface tanto de Vehiculo como de Volador.
5-Visibilidad
Los modificadores de acceso se utilizan para restringir la accesibilidad de clases, objetos, interfaces, constructores, funciones, propiedades y sus setters a un determinado nivel. No es necesario establecer la visibilidad de los getters porque tienen la misma visibilidad que la propiedad.
¿Cuál es la visibilidad de la clase Persona, la propiedad int y de la función display?
¿Se puede instanciar la clase TestPrivada desde la función main()? ¿Por qué?
¿Se puede acceder a la propiedad int desde la función main()? ¿Por qué?
¿Se puede acceder a la función display() desde la función main()? ¿Por qué?
La clase B es subclase de A: ¿es posible acceder a m1, m2, m3 y m4() desde la clase B? ¿Qué propiedades o funciones se pueden sobrescribir en B?
Módulos
El modificador internal (interno) se añadió en Kotlin y significa que el elemento declarado estará disponible en el mismo módulo. Si intentamos acceder desde otro módulo dará un error. Un módulo significa un grupo de archivos que se compilan juntos. Es un modificador que no está soportado por Java. El modificador internal es apropiado para la escritura de APIs e implementaciones.
package org.ejemplos
internal class A {
}
public class B {
internal val int = 10
internal fun display() {
}
}
Existen cuatro modificadores de acceso en Kotlin: private, protected, internal, y public. La visibilidad por defecto es public. Se aplican a declaraciones de clases, funciones o variables miembros de una clase y a aquellas que se ubican en un archivo Kotlin (Declaraciones de Nivel Superior o Top Level).
Ejemplo de declaraciones de nivel superior:
// nombre de archivo: ejemplo.kt
package laboratorio
private fun unaFuncion() { } // solamente visible desde visible dentro de ejemplo.kt
public var miPropiedad: Int = 5 // esta propiedad es visible en todos lados
private set // el setter es solo visible en ejemplo.kt
internal val miPropiedadInmitable = 6 // visible dentro del mismo módulo