1. Introduccion
Cuando empiezas a escribir en Kotlin, a veces parece un profesor estricto: «No, asi no se puede. Y asi tampoco. Y esto tampoco. Y en general, ¿por que me miras asi?» En realidad no es quisquillosidad, sino una proteccion contra errores silenciosos, que en otros lenguajes pueden cambiar imperceptiblemente el significado del programa. En Kotlin, casi cualquier conversion de tipos numericos se hace de forma explicita, es decir, lo escribes tu mismo: que es exactamente lo que quieres.
Imagina: calculas un descuento en porcentaje, lo guardas como Double y luego, en algun sitio, «por accidente» lo conviertes en Int, y la parte fraccionaria desaparece. Si el lenguaje lo hace automaticamente, puedes pasarte semanas con el bug «el descuento a veces sale raro» hasta que pilles el momento. Kotlin te obliga a escribir .toInt(), y con ello como si preguntara: «¿Seguro que quieres tirar la fraccion? ¿Seguro-seguro?»
Esta regla no solo afecta a las fracciones. A Kotlin no le gusta la «ampliacion implicita» como Int → Double, aunque sea segura en cuanto a significado. Por eso, en lugar de «magia», tenemos metodos simples y legibles: toDouble(), toFloat(), toInt(), y en el mundo de los simbolos, tambien la relacion Char ↔ numero.
2. Conversiones basicas de numeros
Cuando ves una llamada como x.toInt(), es util pensar no «Kotlin hizo algo por ahi», sino «yo le pedi a Kotlin que hiciera una operacion concreta». Es un habito importante: si no formulas la accion, luego sera dificil explicarte (y explicarselo a un mentor) por que el resultado «se fue de madre».
A continuacion, una tabla sencilla que ayuda a tener en la cabeza el significado de las conversiones basicas:
| Conversion | Ejemplo | Que ocurre | Donde puede haber perdida de datos |
|---|---|---|---|
|
|
el numero pasa a ser decimal, el valor se conserva | normalmente no |
|
|
el numero pasa a ser decimal, pero Float es menos preciso | a veces si (en numeros grandes) |
|
|
la parte fraccionaria se descarta (¡no se redondea!) | si, siempre que haya fraccion |
|
|
puede convertirse en Infinity o distorsionarse mucho | si |
|
|
la precision no «se recupera», pero el tipo pasa a ser Double | los datos ya podian haberse perdido antes |
Ahora no nos metemos en bits ni en la estructura de memoria (es un tema aparte), pero la conclusion practica es simple: convertir hacia abajo en tamano de tipo (por ejemplo, Double → Int, Double → Float) casi siempre es potencialmente peligroso.
Mira esto en codigo.
fun main() {
val x = 10
val d = x.toDouble()
println("x = $x") // x = 10
println("d = $d") // d = 10.0
}
Aqui todo esta perfecto: Int → Double conserva el valor, simplemente anade la «posibilidad de tener parte fraccionaria».
Y ahora, un ejemplo donde se pierden datos:
fun main() {
val price = 199.99
val asInt = price.toInt()
println("price = $price") // price = 199.99
println("asInt = $asInt") // asInt = 199
}
Ultimo recordatorio: toInt() no redondea, sino que corta la parte fraccionaria (en esencia, se mueve hacia cero). Si quieres redondear «como una persona», lo haces por separado (ya conoces round/floor/ceil de la leccion anterior), y solo despues conviertes el tipo.
3. Mezcla de tipos y perdida imperceptible de significado
Cuando escribes programas pequenos de aprendizaje, parece que las conversiones de tipos son algo raro. Pero en cuanto aparece un calculo «de verdad» (promedio, porcentaje, probabilidad, dinero), de repente resulta que la mitad de los bugs no estan en if ni en bucles, sino en el tipo equivocado en el lugar equivocado.
Un escenario muy tipico: queremos obtener un valor medio. Tenemos la suma (Int) y la cantidad (Int). Si hacemos la division como Int / Int, la parte fraccionaria se pierde. Por eso hay que pasar explicitamente a Double al menos en una parte de la expresion.
fun main() {
val sum = 7
val count = 2
val avg1 = sum / count
val avg2 = sum.toDouble() / count
println("avg1 = $avg1") // avg1 = 3
println("avg2 = $avg2") // avg2 = 3.5
}
Empieza a formar el pensamiento de programador: no «arreglamos Kotlin», sino que formulamos la regla de calculo. Si queremos fraccion, hacemos fraccion.
Otro caso frecuente es mezclar Float y Double. A Kotlin no le gusta que mezcles por accidente dos tipos distintos de decimales. Normalmente es mas facil elegir Double como tipo principal, y usar Float solo si hay una razon clara.
4. Char: un solo caracter y por que es util
Ahora pasamos a un tipo nuevo: Char. En Kotlin, Char es un solo caracter, que se escribe entre comillas simples: 'A', '7', '?'. Los literales Char van con comillas simples, y los caracteres especiales se definen mediante secuencias de escape.
¿Por que es importante? Porque un principiante a menudo confunde:
- '7' — el caracter siete (tipo Char)
- "7" — una cadena de longitud 1 (tipo String)
- 7 — un numero (tipo Int)
Y son tres entidades distintas. A veces se pueden convertir entre si, pero Kotlin no lo hace automaticamente para que no hagas una tonteria sin querer (y lo harias, lo se; todos somos asi).
Ademas, Char aparece mucho en cadenas: una cadena es una secuencia de caracteres y puedes coger caracteres individuales por indice.
Caracteres de escape: '\n', '\t' y otros
Despues de conocer las cadenas, es util entender que algunos caracteres son dificiles de escribir «tal cual». Por eso existen las secuencias de escape: '\n' — nueva linea, '\t' — tabulacion, etc. Tambien pertenecen a Char.
fun main() {
val a = 'A'
val tab = '\t'
val nl = '\n'
println("[$a]") // [A]
println("x${tab}y") // x y
println("line1${nl}line2") // line1
// line2
}
Aqui tab y nl son exactamente caracteres, no comandos magicos para imprimir.
5. Char y numeros: codigos, toChar() y digitos
Ahora viene una parte que te convierte un poco mas en un «chaman informatico». La idea es simple: un caracter tiene un codigo numerico. En la JVM, Char es un simbolo Unicode de 16 bits. Esto significa que un caracter se puede tratar como un numero y, al reves, un numero se puede convertir en un caracter (si cae dentro del rango valido).
En Kotlin, un caracter tiene la propiedad code, que devuelve el codigo numerico del caracter (Int). Y la operacion inversa es toChar() en un numero.
fun main() {
val ch: Char = 'A'
val code: Int = ch.code
val back: Char = code.toChar()
println("ch = $ch") // ch = A
println("code = $code") // code = 65
println("back = $back") // back = A
}
Parece un truco, pero en realidad son solo dos conversiones inversas.
El ejemplo practico mas popular son los digitos. La entrada desde el teclado suele llegar como texto. Incluso si no parseas un Int completo, a veces necesitas tomar un solo digito como caracter, comprobar que es un digito y convertirlo en numero.
Ademas, con los codigos se pueden hacer «desplazamientos»: 'A' + 1 → 'B', '0' + 7 → '7'. En Kotlin, esta idea aparece, por ejemplo, en tareas donde se comparan caracteres y se transforman como numeros.
«Caracter digito» → numero sin magia
Convertir '7' en el numero 7 es un clasico. Los principiantes suelen intentar algo como:
- "7".toInt() — esto funciona para una cadena, pero no para un Char
- ('7').toInt() — no se puede, porque Char no se convierte automaticamente en el «valor del digito»
- 7.toChar() — esto va en la direccion contraria y no dara '7', sino un caracter con codigo 7 (y eso es un caracter de control)
El enfoque correcto es este: si tenemos el caracter ch, y estamos seguros de que esta en el rango '0'..'9', entonces el valor del digito es la diferencia de codigos: digit = ch.code - '0'.code
Primero comprobaremos que realmente es un digito.
fun main() {
val ch = '7'
val isDigit = ch >= '0' && ch <= '9'
val digit = if (isDigit) ch.code - '0'.code else -1
println("isDigit = $isDigit") // isDigit = true
println("digit = $digit") // digit = 7
}
Aqui -1 es solo una «senal de error» dentro del ejemplo: si no es un digito, devolvemos un valor que seguro que no puede ser un digito. Mas adelante, cuando tengamos enfoques mas serios para errores, lo haremos de otra manera, pero por ahora es suficiente.
Es importante notar que este estilo (comparacion de caracteres, trabajo con rangos y conversion mediante codigos) no es un invento por belleza: realmente aparece en codigo Kotlin real, especialmente en tareas algoritmicas.
6. Mini-escenario: generamos un cupon con letras y digitos
Imaginemos que hacemos una mini-app de consola llamada LuckyCoupon. Imprime al usuario el descuento en porcentaje y un codigo de cupon con formato A7Q2 (letra–digito–letra–digito). Sin bucles: solo cuatro pasos, para no distraernos de lo esencial.
La idea clave: para obtener una letra aleatoria, tomamos un numero aleatorio 0..25, lo sumamos al codigo de 'A' y lo convertimos de vuelta a Char.
import kotlin.random.Random
fun main() {
val letter1 = ('A'.code + Random.nextInt(0, 26)).toChar() // 'A'..'Z'
val digit1 = ('0'.code + Random.nextInt(0, 10)).toChar() // '0'..'9'
val letter2 = ('A'.code + Random.nextInt(0, 26)).toChar()
val digit2 = ('0'.code + Random.nextInt(0, 10)).toChar()
val code = "$letter1$digit1$letter2$digit2"
println("Tu cupon: $code") // ejemplo: A7Q2
}
Fijate en dos cosas importantes.
Primera: aqui no hay «magia de conversiones». Trabajamos explicitamente con codigos mediante .code y toChar(). Esto hace el codigo un poco mas largo, pero mucho mas claro: de un numero hicimos el codigo del caracter, del codigo hicimos el caracter.
Segunda: usamos cuidadosamente los limites nextInt(0, 26) y nextInt(0, 10). El limite superior no se incluye, lo que significa que cubrimos exactamente las 26 letras del alfabeto latino, sin sorpresas.
Descuento y el lugar donde a menudo se pierde la fraccion
Supongamos que el descuento se calcula como Double (por ejemplo, te sale un descuento «flotante»). Para mostrarlo queremos el porcentaje como numero entero. Aqui es importante elegir: ¿vamos a cortar la fraccion o a redondear? Ahora mostraremos justo la perdida de fraccion, para ver la diferencia.
fun main() {
val discount = kotlin.random.Random.nextDouble() * 30.0 // 0.0..30.0
val discountInt = discount.toInt() // cortamos la fraccion
println("discount = $discount") // discount = 12.345678...
println("discountInt = $discountInt%") // discountInt = 12%
}
Si quieres redondear «segun las reglas», ahora ya sabes que debes hacerlo en un paso aparte.
7. Esquema-recordatorio de conversiones
A veces es util tener en la cabeza no metodos sueltos, sino una pequena «ruta» de resolucion. Aqui tienes un diagrama sencillo que ayuda a no confundir «corte de fraccion» con «redondeo», y tambien a recordar que los caracteres viven en el mundo de los codigos.
flowchart TD
A["Tenemos un valor"] --> B{Es un numero o un caracter?}
B -->|Numero| C{Necesitamos otro tipo numerico?}
C -->|Si| D["Explicito: toDouble/toFloat/toInt"]
C -->|No| E["Lo dejamos tal cual"]
D --> F{Pasamos de Double a Int?}
F -->|Si| G["Entendemos: la fraccion se perdera"]
F -->|No| H["Suele ser mas seguro"]
B -->|Caracter Char| I["Tomamos ch.code (Int)"]
I --> J{Queremos un digito?}
J -->|Si| K["Comprobamos '0'..'9' y restamos '0'.code"]
J -->|No| L["Convertimos numeros ↔ caracteres con toChar()"]
8. Errores tipicos
Error n. 1: confundir '7', "7" y 7.
Son tres tipos distintos: Char, String y Int. Si guardas un digito como Char, no es el numero 7 y no es la cadena "7". Intentar aplicar a un Char metodos de cadenas, o esperar que un Char «se convierta en numero» por si solo, es una causa frecuente de confusion y de conversiones extranas.
Error n. 2: pensar que toInt() redondea.
toInt() no redondea: descarta la parte fraccionaria. Esto es especialmente desagradable cuando esperas «un descuento 12.9% se convertira en 13%» y obtienes 12%. Aqui Kotlin no tiene la culpa: hizo exactamente lo que le pediste. Si necesitas la matematica del redondeo, primero redondea y luego conviertes a Int.
Error n. 3: hacer Double → Int «en algun momento despues», sin pensar en el significado.
A veces escriben «por ahora que sea Double, y al final lo convierto a Int». Es peligroso: puedes perder datos en un momento en el que ya es tarde para entender de donde salio el error. Mejor mantener un acuerdo: donde guardamos el calculo preciso (Double) y donde exactamente necesitamos un resultado entero (Int), y hacer la conversion en un lugar claro.
Error n. 4: convertir un digito de Char a Int sin comprobar el rango.
La expresion ch.code - '0'.code es correcta solo si ch esta realmente en el rango '0'..'9'. Si ahi aparece 'A', obtendras algun numero (por ejemplo, 17) y el programa no fallara: simplemente empezara a hacer tonterias. Por eso: primero la comprobacion, luego la conversion.
Error n. 5: olvidar que Char es un caracter Unicode, no una «letra de la tabla ASCII».
En ejemplos de aprendizaje, usamos tranquilamente 'A'..'Z' y '0'..'9', y funciona de forma predecible. Pero es importante recordar que Char en Kotlin es Unicode, y las «letras» no son solo latinas. Por eso es mas correcto pensar en terminos de «codigo de caracter» y rangos concretos que en terminos de «bueno, es una letra, asi que +1 sera la siguiente».
GO TO FULL VERSION