I/O: entrada y salida de datos en consola



Dentro de la biblioteca estándar de Kotlin, el paquete kotlin.io proporciona elementos esenciales para trabajar con los flujos de entrada y salida estándar (Input/Output o I/O). Esta transmisión de información entre la memoria principal y los dispositivos de entrada y salida permite, entre otras cosas, leer y escribir datos.

En esta entrada vamos a tratar sobre cómo se transfieren datos de entrada y salida en consola (el paquete kotlin.io también proporciona herramientas para trabajar con archivos, como veremos en futuras entradas), lo que es útil para mostrar una información en pantalla y para obtener información aportada por el usuario, habitualmente a través de un dispositivo de entrada como el teclado.

Output: Escribir en consola

Como ya hemos visto en multitud de ejemplos previos, para enviar un mensaje a la salida estándar (la pantalla) usamos habitualmente las funciones print() y println(), que se diferencian en que la segunda incluye un salto de línea al final.
fun main(args : Array<String>) {
    println("1. println ")
    println("2. println ")

    print("1. print ")
    print("2. print ")
}
El programa anterior obtiene este resultado:
1. println 
2. println 
1. print 2. print 
Podemos añadir un salto de línea en la función print (aunque para eso ya tenemos a println):
print("Función print con salto de línea\n")
Un ejemplo que utiliza la función println para mostrar valores en consola:
val numero = 12.3
println("numero")               // numero
println(numero)                 // 12.3
println("$numero")              // 12.3
println("numero = $numero")     // numero = 12.3
println("${numero + numero}")   // 24.6
println(12.3)                   // 12.3

Input: Lectura de datos en consola

Para la lectura de datos por teclado utilizamos la función readLine (otra opción que no vamos a ver ahora es utilizar la clase Scanner importada desde la librería estándar de Java con import java.util.Scanner):
fun main(args: Array<String>) {
    print("Tu nombre: ")
    val nombre = readLine()
    println("Hola $nombre")
}
La función readLine() convierte la entrada en un String (realmente devuelve un String? puesto que puede ser nulo) aunque es posible tomar la entrada y convertirla a otro tipo explícitamente:
print("Nombre: ")
val nombre = readLine()
print("Edad: ")
val edad = Integer.valueOf(readLine()) // cuidadín!!   
println("Tu nombre es $nombre y tienes $edad años.")
El programa anterior funcionará bien mientras el usuario introduzca un entero como valor de edad, pero en caso contrario saltará un excepción durante la ejecución (NumberFormatException). Una primera solución podría pasar por el uso del manejo de excepciones (que veremos en posteriores entradas) para capturar los casos problemáticos:
print("Nombre: ")
val nombre = readLine()
print("Edad: ")
try {
    val edad = Integer.valueOf(readLine())
    println("Tu nombre es $nombre y tienes $edad años.")
} catch (ex: NumberFormatException) {
    println("Edad no válida")
}
A veces se pueden ver ejemplos de código que intentan resolver este problema con el operador de aserción !! (revisar Gestión de tipos nulos en Kotlin), y de esta manera se le avisa al compilador que confíe que la función readLine siempre retornará un String, pero en general no es una buena manera de validar la entrada de datos.
val num: Int
print("Introduce un número entero: ")
num = readLine()!!.toInt() // prometemos algo que no podemos cumplir
println(num)
A pesar de la pretendida seguridad del operador !!, seguimos sin escapar del NumberFormatException. Recuerda que además de la función toInt() también existen las funciones toFloat(), toDouble(), toLong(), toShort() y toByte() para la conversión de tipos (ver Tipos básicos de datos).

Existen distintas formas de resolver éste y otros problemas similares respecto a la entrada de datos por parte del usuario. Algunas soluciones pasan por combinar la función readLine con el operador de llamada segura ? y con la expresión try para devolver un valor (otras soluciones también utilizan el operador as que vimos en Comprobación y conversión de tipos con is y as). Un ejemplo:
val num: Int?
print("Introduce un número entero: ")
num = try {
    readLine()?.toInt()
} catch (ex: NumberFormatException) {
    null
}
if (num != null) {
    println("El número es: $num")
} else {
    println("¡Eso no es un número entero!")
}
En este ejemplo el valor introducido es asignado a la variable anulable num, cuyo valor depende de que se procese el contenido de la expresión try (cuando se ingresa un entero) o de que, en caso contrario, se produzca un NumberFormatException que es capturado por catch, que retorna null que es asignado a la variable num.

Pero podemos mejorarla prescindiendo de try..catch y sustituyendo la función toInt por la función toIntOrNull:
val num: Int?
print("Introduce un número entero: ")
num = readLine()?.toIntOrNull()
if (num != null) {
    println("El número es: $num")
} else {
    println("¡Eso no es un número entero!")
}
En el ejemplo anterior llamamos a readLine con el operador ? para realizar la conversión con toIntOrNull de forma segura. La función toIntOrNull() requiere que la variable sea de tipo anulable (val num: Int?) porque si la conversión a entero falla, se retorna null, que es asignado a num. Igualmente contamos con las funciones toFloatOrNull(), toDoubleOrNull(), toLongOrNull(), toShortOrNull() y toByteOrNull() que en caso de no poder realizar la conversión de tipos devuelven null.

Comentarios

Entradas populares

Recursos gratis para aprender Kotlin

Lectura y escritura de archivos