Los operadores son los verbos del lenguaje: lo que convierte datos inertes en cálculos. Ya has usado + y - de forma intuitiva, pero Java tiene un catálogo amplio y varias trampas que atrapan a todo el mundo al menos una vez: la división entre enteros que descarta los decimales, la diferencia entre i++ e ++i, la comparación de cadenas con == que a veces funciona y a veces no, y la precisión traicionera de double cuando hay dinero de por medio. Esta lección los recorre todos con calma, siempre sobre el mismo caso de BiblioTech: calcular la multa por retraso de un préstamo de la biblioteca de Nexus Software.
Contenido
- Operadores aritméticos y la trampa de la división entera
- El operador módulo
- Operadores de asignación compuesta
- Incremento y decremento: pre y post
- Operadores relacionales
- Operadores lógicos y evaluación de cortocircuito
- El operador ternario
- Operadores a nivel de bit y desplazamiento
- El operador
instanceof - Precedencia y asociatividad
- Comparar cadenas:
==frente aequals - La precisión de
doubley por qué existeBigDecimal - Errores Comunes y Consejos
- Ejercicios
- Operadores aritméticos y la trampa de la división entera
Los cinco operadores aritméticos básicos:
| Operador | Nombre | Ejemplo | Resultado |
|---|---|---|---|
+ |
Suma | 12 + 3 |
15 |
- |
Resta | 27 - 15 |
12 |
* |
Multiplicación | 12 * 2 |
24 |
/ |
División | 12 / 5 |
2 (¡atención!) |
% |
Módulo (resto) | 12 % 5 |
2 |
La trampa del 5 / 2
Es el error número uno de quien empieza en Java:
La regla es la siguiente: si ambos operandos son enteros, la división es entera y el resultado se trunca hacia cero, descartando los decimales. Java no redondea, corta.
Y esto no se arregla guardando el resultado en un double:
¿Por qué? Porque la operación total / partes se evalúa primero, entre enteros, dando 2; solo después ese 2 se convierte a double. La conversión llega demasiado tarde.
Las tres soluciones correctas:
// 1. Cast de uno de los operandos ANTES de dividir
double resultado = (double) total / partes; // 2.5
// 2. Declarar las variables como double desde el principio
double totalD = 5;
double partesD = 2;
double resultado2 = totalD / partesD; // 2.5
// 3. Multiplicar por 1.0 (truco menos legible, pero se ve en codigo real)
double resultado3 = total * 1.0 / partes; // 2.5Ojo con el orden en el cast: (double)(total / partes) no funciona. Los paréntesis hacen que se divida primero entre enteros y se convierta después: da 2.0. El cast tiene que aplicarse a uno de los operandos.
División por cero
Depende del tipo, y la diferencia es importante:
int a = 5 / 0; // ArithmeticException: / by zero -> el programa se detiene
double b = 5.0 / 0; // Infinity -> el programa CONTINUA
double c = 0.0 / 0; // NaN (Not a Number) -> el programa CONTINUALa división entera por cero lanza un error de ejecución. La división decimal produce valores especiales (Infinity, NaN) que se propagan silenciosamente por todos los cálculos posteriores, lo que puede ser peor: acabas mostrando NaN EUR en un recibo sin haber visto ningún error.
Aplicado a BiblioTech
final int DIAS_PRESTAMO = 15;
int diasTranscurridos = 27;
int diasRetraso = diasTranscurridos - DIAS_PRESTAMO; // 12 dias de retraso
final double TARIFA_DIARIA = 0.25;
double multa = diasRetraso * TARIFA_DIARIA; // 12 * 0.25 = 3.0
System.out.println("Dias de retraso: " + diasRetraso);
System.out.println("Multa: " + multa + " EUR");Aquí diasRetraso es int y TARIFA_DIARIA es double: Java promociona el entero a decimal antes de multiplicar y el resultado es correcto. La promoción automática nos salva; la división es la única que hay que vigilar.
- El operador módulo
% devuelve el resto de una división entera. Es mucho más útil de lo que parece a primera vista.
System.out.println(17 % 5); // 2 (17 = 5*3 + 2)
System.out.println(12 % 4); // 0 (division exacta)
System.out.println(3 % 5); // 3 (3 = 5*0 + 3)
System.out.println(-7 % 3); // -1 (en Java el signo lo da el DIVIDENDO)
System.out.println(7.5 % 2); // 1.5 (tambien funciona con decimales)Usos habituales:
| Objetivo | Expresión | Explicación |
|---|---|---|
| ¿Es par? | n % 2 == 0 |
Resto cero al dividir entre 2 |
| ¿Es múltiplo de 7? | n % 7 == 0 |
Generalización del anterior |
| Último dígito | n % 10 |
Resto de dividir entre 10 |
| Ciclar en un rango 0..n−1 | i % n |
Nunca se sale del rango |
| Descomponer tiempo | minutos % 60 |
Minutos que sobran de las horas completas |
Aplicado a BiblioTech, para expresar el retraso en semanas y días sueltos:
int diasRetraso = 27;
int semanasRetraso = diasRetraso / 7; // division entera: 3 semanas
int diasSueltos = diasRetraso % 7; // resto: 6 dias
System.out.println("Retraso: " + semanasRetraso + " semanas y " + diasSueltos + " dias");
// Retraso: 3 semanas y 6 diasFíjate en cómo / y % trabajan en pareja: uno da el cociente, el otro lo que sobra. Es un patrón que reaparecerá muchas veces.
- Operadores de asignación compuesta
Son atajos para operar sobre una variable y guardar el resultado en ella misma:
| Operador | Equivalente a | Ejemplo |
|---|---|---|
+= |
x = x + y |
multa += 0.25; |
-= |
x = x - y |
diasRetraso -= 3; |
*= |
x = x * y |
multa *= 2; |
/= |
x = x / y |
total /= 4; |
%= |
x = x % y |
contador %= 7; |
Hay un detalle poco conocido: la asignación compuesta incluye un cast implícito. Esto compila:
int diasRetraso = 12;
diasRetraso += 0.75; // compila: equivale a diasRetraso = (int)(diasRetraso + 0.75)
System.out.println(diasRetraso); // 12 <- el 0.75 se pierde en el truncamientomientras que la forma "larga" equivalente no compila:
Es una asimetría del lenguaje que puede ocultar pérdidas de precisión sin ningún aviso. Merece la pena conocerla.
- Incremento y decremento: pre y post
++ suma 1 y -- resta 1. Lo interesante es que se pueden escribir antes o después de la variable, y eso cambia el valor de la expresión:
- Post-incremento
x++: usa el valor actual, y después incrementa. - Pre-incremento
++x: incrementa primero, y después usa el valor nuevo.
int a = 5;
int resultadoA = a++; // resultadoA recibe 5, y luego a pasa a valer 6
System.out.println("a = " + a + ", resultadoA = " + resultadoA);
// a = 6, resultadoA = 5
int b = 5;
int resultadoB = ++b; // b pasa a valer 6 primero, y resultadoB recibe 6
System.out.println("b = " + b + ", resultadoB = " + resultadoB);
// b = 6, resultadoB = 6La tabla resume la diferencia partiendo siempre de x = 5:
| Expresión | Valor de la expresión | Valor final de x |
|---|---|---|
x++ |
5 | 6 |
++x |
6 | 6 |
x-- |
5 | 4 |
--x |
4 | 4 |
Cuándo importa de verdad: si el ++ está solo en su propia línea (contador++;), pre y post son idénticos y da igual cuál uses. La diferencia solo se manifiesta cuando el valor de la expresión se usa para algo, como en una asignación, un índice o un argumento.
Consejo práctico: no escribas expresiones que dependan de esa sutileza. Código como int x = i++ + ++i; es un acertijo, no un programa. Cuando importe, sepáralo en dos líneas claras.
- Operadores relacionales
Comparan dos valores y siempre producen un boolean:
| Operador | Significado | Ejemplo | Resultado |
|---|---|---|---|
== |
Igual a | diasRetraso == 0 |
false si hay retraso |
!= |
Distinto de | diasRetraso != 0 |
true si hay retraso |
> |
Mayor que | 27 > 15 |
true |
< |
Menor que | 12 < 15 |
true |
>= |
Mayor o igual | 15 >= 15 |
true |
<= |
Menor o igual | 20 <= 15 |
false |
final int DIAS_PRESTAMO = 15;
int diasTranscurridos = 27;
boolean hayRetraso = diasTranscurridos > DIAS_PRESTAMO;
System.out.println("Hay retraso: " + hayRetraso); // trueRepite la idea de la lección anterior: una comparación es una expresión que vale true o false por sí misma. No necesitas un if para obtenerla; el if (módulo 2) sirve para decidir qué hacer con ella.
Dos advertencias:
==compara valores en primitivos, pero referencias en objetos. Es el tema del apartado 11.- El error tipográfico más caro de la programación es escribir
=(asignación) donde querías==(comparación). En Java, afortunadamente,if (x = 5)no compila salvo quexseaboolean, porque una asignación deintno produce unboolean. El compilador te protege.
- Operadores lógicos y evaluación de cortocircuito
Combinan valores booleanos:
| Operador | Nombre | Resultado |
|---|---|---|
&& |
Y lógico (AND) | true solo si ambos son true |
|| |
O lógico (OR) | true si al menos uno es true |
! |
Negación (NOT) | Invierte el valor |
Tablas de verdad:
a |
b |
a && b |
a || b |
!a |
|---|---|---|---|---|
true |
true |
true |
true |
false |
true |
false |
false |
true |
false |
false |
true |
false |
true |
true |
false |
false |
false |
false |
true |
Aplicado a BiblioTech:
boolean disponible = false;
int diasRetraso = 12;
boolean esEmpleadoActivo = true;
boolean estaPrestadoYConRetraso = !disponible && diasRetraso > 0; // true
boolean necesitaAviso = diasRetraso > 0 || !disponible; // true
boolean puedeReservar = disponible && esEmpleadoActivo; // falseLa evaluación de cortocircuito
Esta es la parte importante. && y || son perezosos: dejan de evaluar en cuanto el resultado ya está decidido.
- En
a && b, siaesfalse, el resultado esfalsepase lo que pase conb.bno se evalúa. - En
a || b, siaestrue, el resultado estruepase lo que pase conb.bno se evalúa.
flowchart TD
A["evaluar a"] --> B{"¿qué operador?"}
B -- "AND lógico" --> C{"¿a es false?"}
C -- "Sí" --> D["resultado false<br/><b>b NO se evalúa</b>"]
C -- "No" --> E["evaluar b<br/>resultado = b"]
B -- "OR lógico" --> F{"¿a es true?"}
F -- "Sí" --> G["resultado true<br/><b>b NO se evalúa</b>"]
F -- "No" --> H["evaluar b<br/>resultado = b"]
¿Por qué importa? Porque permite proteger operaciones peligrosas colocando la comprobación de seguridad primero:
String empleado = null;
// Esto FALLA con NullPointerException: se intenta llamar a length() sobre null
boolean valido = empleado.length() > 0 && empleado != null;
// Esto FUNCIONA: si empleado es null, la primera condicion es false
// y la segunda ni siquiera se evalua
boolean valido = empleado != null && empleado.length() > 0;El orden de las condiciones no es estético: es funcional. Recuérdalo, porque es un patrón que usarás constantemente a partir del módulo 6.
Las versiones sin cortocircuito: & y |
Existen & y | aplicados a booleanos, que siempre evalúan ambos lados:
boolean r1 = false && metodoQueImprime(); // el metodo NO se ejecuta
boolean r2 = false & metodoQueImprime(); // el metodo SI se ejecutaEn la práctica se usan && y || casi siempre. Recuerda que & y | tienen además otro significado completamente distinto sobre números, que es el siguiente apartado.
- El operador ternario
Es el único operador de Java con tres operandos. Su forma es:
Se lee: "si la condición es verdadera, el resultado es lo primero; si no, lo segundo".
int diasTranscurridos = 27;
final int DIAS_PRESTAMO = 15;
int diasRetraso = diasTranscurridos > DIAS_PRESTAMO
? diasTranscurridos - DIAS_PRESTAMO
: 0;
System.out.println("Dias de retraso: " + diasRetraso); // 12Esta línea hace algo muy valioso para nosotros: evita que el retraso sea negativo. Si el libro se devuelve a los 10 días, 10 - 15 daría -5, y una multa negativa sería un absurdo (¡la biblioteca pagando al empleado!). El ternario nos permite corregirlo sin usar if, que todavía no hemos visto.
El uso más habitual es adaptar un texto:
double multa = diasRetraso * 0.25;
String estado = diasRetraso > 0 ? "CON RETRASO" : "EN PLAZO";
String moneda = multa == 1.0 ? "euro" : "euros";
System.out.println("Estado del prestamo: " + estado);
System.out.println("Importe: " + multa + " " + moneda);Se pueden anidar, pero la legibilidad se degrada muy rápido:
// Legible al limite
String categoria = diasRetraso == 0 ? "PUNTUAL"
: diasRetraso <= 7 ? "LEVE"
: "GRAVE";Regla práctica: el ternario es excelente para elegir un valor; en cuanto quieras ejecutar varias acciones distintas, necesitas un if, que llega en la lección 02-01.
- Operadores a nivel de bit y desplazamiento
Estos operadores trabajan sobre la representación binaria de los números enteros. Al principio parecen esotéricos, pero aparecen en permisos, banderas, criptografía, procesamiento de imágenes y protocolos de red, y en las entrevistas técnicas.
Recordatorio: un int son 32 bits. El número 12 es 00000000 00000000 00000000 00001100, y el 10 es ...00001010.
Los operadores bit a bit
| Operador | Nombre | Regla por bit | 12 op 10 |
Resultado |
|---|---|---|---|---|
& |
AND | 1 solo si ambos son 1 | 1100 & 1010 |
1000 = 8 |
| |
OR | 1 si alguno es 1 | 1100 | 1010 |
1110 = 14 |
^ |
XOR | 1 si son distintos | 1100 ^ 1010 |
0110 = 6 |
~ |
NOT | Invierte todos los bits | ~12 |
-13 |
int a = 12; // 1100
int b = 10; // 1010
System.out.println(a & b); // 8 -> 1000
System.out.println(a | b); // 14 -> 1110
System.out.println(a ^ b); // 6 -> 0110
System.out.println(~a); // -13
// Ver la representacion binaria es la mejor forma de entenderlo
System.out.println(Integer.toBinaryString(a)); // 1100
System.out.println(Integer.toBinaryString(a & b)); // 1000El resultado de ~12 = -13 sorprende. La razón es que Java representa los enteros negativos en complemento a dos, donde ~x equivale siempre a -x - 1.
Los operadores de desplazamiento
| Operador | Nombre | Qué hace | Ejemplo | Resultado |
|---|---|---|---|---|
<< |
Desplazamiento a la izquierda | Mueve los bits a la izquierda, rellena con ceros | 12 << 2 |
48 |
>> |
Desplazamiento a la derecha con signo | Mueve a la derecha, conserva el signo | 12 >> 2 |
3 |
>>> |
Desplazamiento a la derecha sin signo | Mueve a la derecha, rellena con ceros | -12 >>> 28 |
15 |
La relación con la aritmética es directa y muy fácil de recordar:
x << nequivale a multiplicar por 2ⁿx >> nequivale a dividir entre 2ⁿ (división entera)
System.out.println(12 << 1); // 24 (12 * 2)
System.out.println(12 << 2); // 48 (12 * 4)
System.out.println(12 >> 1); // 6 (12 / 2)
System.out.println(12 >> 2); // 3 (12 / 4)
System.out.println(-12 >> 2); // -3 conserva el signo
System.out.println(-12 >>> 2); // 1073741821 rellena con ceros: el signo se pierdeUn uso realista: banderas de permisos
El caso donde estos operadores brillan de verdad es empaquetar varias opciones booleanas en un solo entero. En BiblioTech podríamos codificar los permisos de un empleado sobre el catálogo:
// Cada permiso ocupa un bit distinto
final int PERMISO_CONSULTAR = 1; // 0001
final int PERMISO_PRESTAR = 2; // 0010
final int PERMISO_RESERVAR = 4; // 0100
final int PERMISO_ADMINISTRAR = 8; // 1000
// Marta Ruiz puede consultar, tomar prestado y reservar
int permisosMarta = PERMISO_CONSULTAR | PERMISO_PRESTAR | PERMISO_RESERVAR; // 0111 = 7
// Comprobar un permiso concreto: AND con la bandera
boolean puedeReservar = (permisosMarta & PERMISO_RESERVAR) != 0; // true
boolean puedeAdministrar = (permisosMarta & PERMISO_ADMINISTRAR) != 0; // false
System.out.println("Permisos de Marta Ruiz: " + permisosMarta);
System.out.println("Puede reservar: " + puedeReservar);
System.out.println("Puede administrar: " + puedeAdministrar);Cuatro booleanos en un solo int, con comprobaciones instantáneas. Es el mecanismo que usan por debajo los permisos de ficheros de Unix y muchísimas APIs.
- El operador
instanceof
instanceofinstanceof comprueba si un objeto es de un tipo determinado y devuelve un boolean:
Solo se menciona aquí para completar el catálogo de operadores. Su verdadera utilidad aparece cuando existen jerarquías de tipos y polimorfismo, es decir, a partir del módulo 3. Desde Java 16 admite además la coincidencia de patrones (if (obj instanceof String s)), que se estudia en el módulo 10.
- Precedencia y asociatividad
Cuando una expresión combina varios operadores, Java aplica un orden fijo. De mayor a menor prioridad:
| Nivel | Operadores | Asociatividad |
|---|---|---|
| 1 | () [] . |
Izquierda a derecha |
| 2 | ++ -- (postfijos) |
Izquierda a derecha |
| 3 | ++ -- (prefijos), + - (unarios), !, ~, casts |
Derecha a izquierda |
| 4 | * / % |
Izquierda a derecha |
| 5 | + - |
Izquierda a derecha |
| 6 | << >> >>> |
Izquierda a derecha |
| 7 | < <= > >= instanceof |
Izquierda a derecha |
| 8 | == != |
Izquierda a derecha |
| 9 | & |
Izquierda a derecha |
| 10 | ^ |
Izquierda a derecha |
| 11 | | |
Izquierda a derecha |
| 12 | && |
Izquierda a derecha |
| 13 | || |
Izquierda a derecha |
| 14 | ? : (ternario) |
Derecha a izquierda |
| 15 | = += -= *= /= %= |
Derecha a izquierda |
La asociatividad decide qué pasa cuando hay operadores del mismo nivel: 10 - 4 - 3 se agrupa como (10 - 4) - 3 = 3, no como 10 - (4 - 3) = 9.
Ejemplos donde la precedencia cambia el resultado:
System.out.println(2 + 3 * 4); // 14, no 20: * va antes que +
System.out.println((2 + 3) * 4); // 20
System.out.println(10 - 4 - 3); // 3: asociativo por la izquierda
// Trampa clasica: & tiene MENOS prioridad que !=
// (a & b) != 0 es lo correcto; a & b != 0 se agrupa como a & (b != 0) y ni compilaY un caso real de BiblioTech donde faltan paréntesis:
int diasTranscurridos = 27;
final int DIAS_PRESTAMO = 15;
final double TARIFA_DIARIA = 0.25;
// MAL: se evalua (diasTranscurridos) - (DIAS_PRESTAMO * TARIFA_DIARIA)
double multa = diasTranscurridos - DIAS_PRESTAMO * TARIFA_DIARIA; // 23.25, absurdo
// BIEN: los parentesis expresan la intencion
double multa = (diasTranscurridos - DIAS_PRESTAMO) * TARIFA_DIARIA; // 3.0El consejo definitivo: no memorices la tabla. Usa paréntesis. Nadie ha perdido nunca una revisión de código por poner paréntesis de más, y sí muchas horas por confiar en la precedencia. Los paréntesis documentan tu intención.
- Comparar cadenas:
== frente a equals
== frente a equalsEs el error clásico por excelencia de Java, y lo peor es que a veces funciona, lo que retrasa el diagnóstico durante semanas.
String a = "Java Efectivo";
String b = "Java Efectivo";
System.out.println(a == b); // true (!)
String c = new String("Java Efectivo");
System.out.println(a == c); // false (!)
System.out.println(a.equals(c)); // truePor qué ocurre: el pool de cadenas
Recuerda de la lección anterior que una variable String no contiene el texto, sino una referencia a un objeto en el montículo. Y == sobre referencias compara si apuntan al mismo objeto, no si el contenido es igual.
La JVM mantiene un pool de cadenas (string pool): una zona especial donde guarda los literales. Cuando compila String a = "Java Efectivo"; y luego String b = "Java Efectivo";, ve que el literal es idéntico y hace que ambas variables apunten al mismo objeto del pool. Por eso a == b da true. Es una optimización de memoria, no una promesa del lenguaje.
Pero new String("Java Efectivo") fuerza la creación de un objeto nuevo, fuera del pool. Ahora hay dos objetos con el mismo texto en direcciones distintas, y == da false.
flowchart LR
subgraph Pila
A["a"]
B["b"]
C["c"]
end
subgraph Pool["String pool"]
P["#p1<br/>'Java Efectivo'"]
end
subgraph Heap["Montículo"]
H["#h9<br/>'Java Efectivo'"]
end
A --> P
B --> P
C --> H
a == b compara #p1 == #p1 → true. a == c compara #p1 == #h9 → false. En ambos casos el texto es el mismo.
La regla, sin excepciones
Para comparar el contenido de dos
String, usa siempreequals(). Nunca==.
String tituloIntroducido = "java efectivo";
String tituloCatalogo = "Java Efectivo";
System.out.println(tituloIntroducido.equals(tituloCatalogo)); // false
System.out.println(tituloIntroducido.equalsIgnoreCase(tituloCatalogo)); // truePor qué el peligro es real: las cadenas que llegan de un Scanner, de un fichero o de la red no están en el pool. Así que tu comparación con == funcionará perfectamente en las pruebas con literales y fallará con datos reales.
Una técnica defensiva habitual es poner el literal a la izquierda, porque un literal nunca es null:
// Si isbn fuera null, esto lanzaria NullPointerException
if (isbn.equals("978-0000000001")) { ... }
// Esto es seguro incluso si isbn es null
if ("978-0000000001".equals(isbn)) { ... }Resumen en tabla:
| Comparación | Con primitivos | Con objetos (String) |
|---|---|---|
== |
Compara valores. Correcto | Compara referencias. Casi siempre incorrecto |
equals() |
No existe (los primitivos no son objetos) | Compara contenido. Correcto |
- La precisión de
double y por qué existe BigDecimal
double y por qué existe BigDecimalUn último aviso, particularmente relevante porque BiblioTech maneja dinero:
System.out.println(0.1 + 0.2); // 0.30000000000000004
System.out.println(0.1 + 0.2 == 0.3); // false
System.out.println(1.03 - 0.42); // 0.6100000000000001Esto no es un fallo de Java: ocurre en Python, JavaScript, C y prácticamente en todos los lenguajes. La causa es que float y double siguen el estándar IEEE 754, que representa los números en binario. Del mismo modo que en decimal no puedes escribir 1/3 exactamente (0,3333…), en binario no puedes escribir 0,1 exactamente. El valor almacenado es una aproximación buenísima, pero aproximación.
Consecuencias prácticas:
- Nunca compares dos
doublecon==. Compara si su diferencia es menor que una tolerancia:Math.abs(a - b) < 0.0001. - Nunca uses
doublepara dinero en un sistema real. Los errores se acumulan a lo largo de miles de operaciones y acaban descuadrando la contabilidad.
La solución de Java es la clase BigDecimal, que representa decimales con precisión exacta y permite controlar el redondeo:
import java.math.BigDecimal;
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b)); // 0.3 exactoTiene su coste: es más verboso (no se puede usar +, hay que llamar a add()) y más lento. Se estudia en el módulo 10; en este curso seguiremos usando double para las multas de BiblioTech porque el foco está en aprender el lenguaje, pero quedas advertido: en un sistema de facturación real, BigDecimal.
Errores Comunes y Consejos
5 / 2da2. División entera. Aplica un cast a uno de los operandos:(double) 5 / 2.(double)(5 / 2)sigue dando2.0. El cast llega después de la división. Debe aplicarse a un operando, no al resultado.- Comparar
Stringcon==. Funciona con literales y falla con datos de entrada. Usaequals()siempre. - Comparar
doublecon==. Usa una tolerancia. - Olvidar paréntesis en
(a - b) * c. La multiplicación tiene más prioridad que la resta. - Confundir
&con&&. Sobre booleanos,&&evita evaluar el segundo operando;&no. Sobre enteros,&es una operación bit a bit completamente distinta. - Ordenar mal las condiciones protegidas.
x != null && x.length() > 0, nunca al revés. - Escribir expresiones acrobáticas con
++. Si tienes que pararte a pensar, refactorízalo en dos líneas. - Consejo: usa paréntesis siempre que la expresión tenga más de dos operadores. No hay penalización de rendimiento y la ganancia en claridad es enorme.
- Consejo:
Integer.toBinaryString(n)es la mejor herramienta para entender los operadores de bits. Pruébalo en JShell. - Consejo: el ternario es perfecto para saturar valores a cero (
x > 0 ? x : 0), un patrón que usarás mucho en BiblioTech para que los días de retraso nunca sean negativos.
Ejercicios
Ejercicio 1: Predecir la salida
Sin ejecutar el código, escribe la salida de cada línea y justifica el porqué.
public class PruebaOperadores {
public static void main(String[] args) {
System.out.println(7 / 2);
System.out.println(7 % 2);
System.out.println(7.0 / 2);
System.out.println((double)(7 / 2));
int x = 5;
System.out.println(x++);
System.out.println(x);
System.out.println(++x);
System.out.println(2 + 3 * 4);
System.out.println(10 - 4 - 3);
String a = "Refactorizacion";
String b = "Refactorizacion";
String c = new String("Refactorizacion");
System.out.println(a == b);
System.out.println(a == c);
System.out.println(a.equals(c));
System.out.println(0.1 + 0.2 == 0.3);
System.out.println(12 & 10);
System.out.println(12 << 2);
}
}Ejercicio 2: Calculadora de multa de BiblioTech
Escribe la clase CalculadoraMulta que, a partir de las siguientes constantes y datos, calcule y muestre la liquidación de un préstamo:
- Constantes:
DIAS_PRESTAMO = 15,TARIFA_DIARIA = 0.25,MULTA_MAXIMA = 20.0,RECARGO_GRAVE = 1.5(multiplicador que se aplica si el retraso supera los 30 días). - Datos del préstamo: empleado
"Diego Alonso", libro"Refactorizacion",diasTranscurridos = 52.
Debe calcular y mostrar:
- Los días de retraso, que nunca pueden ser negativos (usa el operador ternario).
- El retraso expresado en semanas completas y días sueltos (usa
/y%). - La multa base (
diasRetraso * TARIFA_DIARIA). - La multa con recargo si el retraso supera 30 días (usa el ternario).
- La multa final, sin superar nunca
MULTA_MAXIMA(usa el ternario). - Una etiqueta de gravedad:
"EN PLAZO","LEVE"(1-7 días) o"GRAVE"(más de 7), con ternarios anidados.
Todo sin usar if.
Ejercicio 3: Banderas de permisos
Usando los operadores a nivel de bit, escribe la clase PermisosBiblioTech que:
- Defina cuatro constantes de permiso con valores 1, 2, 4 y 8: consultar, prestar, reservar y administrar.
- Componga los permisos de tres empleados de Nexus Software: Marta Ruiz (consultar + prestar), Diego Alonso (consultar + prestar + reservar) y Nuria Vidal (los cuatro).
- Muestre para cada uno el valor entero de sus permisos, su representación binaria y si puede administrar.
- Añada el permiso de reservar a Marta Ruiz usando
|=y muestre el resultado.
Soluciones
Solución 1
Justificaciones:
7 / 2→ 3. Ambos operandos sonint: división entera truncada.7 % 2→ 1. El resto de dividir 7 entre 2.7.0 / 2→ 3.5. Al ser7.0undouble, el2se promociona y la división es decimal.(double)(7 / 2)→ 3.0. Los paréntesis fuerzan la división entera primero (3) y el cast llega después.x++→ 5, y a continuaciónxvale 6. El post-incremento devuelve el valor anterior.x→ 6. Confirma el incremento previo.++x→ 7. El pre-incremento incrementa primero (7) y devuelve el valor nuevo.2 + 3 * 4→ 14.*tiene más precedencia que+: se calcula3*4=12y luego2+12.10 - 4 - 3→ 3. Asociatividad por la izquierda:(10-4)-3.a == b→ true. Ambos son literales idénticos, así que la JVM los comparte en el pool de cadenas y las dos referencias apuntan al mismo objeto.a == c→ false.new String(...)crea un objeto distinto fuera del pool; las referencias difieren aunque el texto sea igual.a.equals(c)→ true.equalscompara el contenido, que sí es idéntico.0.1 + 0.2 == 0.3→ false. La representación IEEE 754 de0.1y0.2es aproximada; la suma da0.30000000000000004.12 & 10→ 8. En binario,1100 & 1010 = 1000, que es 8.12 << 2→ 48. Desplazar 2 bits a la izquierda equivale a multiplicar por 2² = 4.
Solución 2
public class CalculadoraMulta {
public static void main(String[] args) {
// === Reglas de negocio de BiblioTech (Nexus Software) ===
final int DIAS_PRESTAMO = 15; // dias de prestamo sin recargo
final double TARIFA_DIARIA = 0.25; // euros por dia de retraso
final double MULTA_MAXIMA = 20.0; // tope absoluto por prestamo
final double RECARGO_GRAVE = 1.5; // multiplicador si el retraso pasa de 30 dias
final int UMBRAL_RECARGO = 30;
final int UMBRAL_LEVE = 7;
// === Datos del prestamo ===
String empleado = "Diego Alonso";
String titulo = "Refactorizacion";
String isbn = "978-0000000003";
int diasTranscurridos = 52;
// 1. Dias de retraso, saturado a cero con el operador ternario.
// Si se devolviera antes de tiempo, la resta seria negativa y
// produciria una multa negativa, que carece de sentido.
int diasRetraso = diasTranscurridos > DIAS_PRESTAMO
? diasTranscurridos - DIAS_PRESTAMO
: 0; // 52 - 15 = 37
// 2. Descomposicion en semanas y dias: / da el cociente, % el resto.
int semanas = diasRetraso / 7; // 5
int diasSueltos = diasRetraso % 7; // 2
// 3. Multa base. int * double -> Java promociona el int a double.
double multaBase = diasRetraso * TARIFA_DIARIA; // 37 * 0.25 = 9.25
// 4. Recargo por retraso grave, de nuevo con el ternario.
double multaConRecargo = diasRetraso > UMBRAL_RECARGO
? multaBase * RECARGO_GRAVE
: multaBase; // 9.25 * 1.5 = 13.875
// 5. Tope: la multa nunca supera MULTA_MAXIMA.
double multaFinal = multaConRecargo > MULTA_MAXIMA
? MULTA_MAXIMA
: multaConRecargo; // 13.875 < 20 -> 13.875
// 6. Etiqueta de gravedad con ternarios anidados.
// Se evaluan en cadena, de arriba abajo, como una escalera.
String gravedad = diasRetraso == 0 ? "EN PLAZO"
: diasRetraso <= UMBRAL_LEVE ? "LEVE"
: "GRAVE";
// === Salida ===
System.out.println("=== BiblioTech - Liquidacion de prestamo ===");
System.out.println("Empleado: " + empleado);
System.out.println("Libro: " + titulo);
System.out.println("ISBN: " + isbn);
System.out.println("Dias transcurridos: " + diasTranscurridos
+ " (prestamo estandar: " + DIAS_PRESTAMO + " dias)");
System.out.println();
System.out.println("Dias de retraso: " + diasRetraso
+ " (" + semanas + " semanas y " + diasSueltos + " dias)");
System.out.println("Gravedad: " + gravedad);
System.out.println("Multa base: " + multaBase + " EUR");
System.out.println("Con recargo: " + multaConRecargo + " EUR");
System.out.println("MULTA FINAL: " + multaFinal + " EUR");
}
}Salida:
=== BiblioTech - Liquidacion de prestamo === Empleado: Diego Alonso Libro: Refactorizacion ISBN: 978-0000000003 Dias transcurridos: 52 (prestamo estandar: 15 dias) Dias de retraso: 37 (5 semanas y 2 dias) Gravedad: GRAVE Multa base: 9.25 EUR Con recargo: 13.875 EUR MULTA FINAL: 13.875 EUR
Observa el 13.875: tres decimales para un importe en euros. Es exactamente el tipo de resultado que en un sistema real exigiría BigDecimal o, como mínimo, un formateo a dos decimales. Eso último lo resolveremos en la lección siguiente con printf.
Solución 3
public class PermisosBiblioTech {
public static void main(String[] args) {
// 1. Cada permiso ocupa UN bit distinto: potencias de 2.
// Asi ninguno interfiere con los demas al combinarlos.
final int CONSULTAR = 1; // 0001
final int PRESTAR = 2; // 0010
final int RESERVAR = 4; // 0100
final int ADMINISTRAR = 8; // 1000
// 2. Se combinan con OR: activa los bits de ambos operandos.
int permisosMarta = CONSULTAR | PRESTAR; // 0011 = 3
int permisosDiego = CONSULTAR | PRESTAR | RESERVAR; // 0111 = 7
int permisosNuria = CONSULTAR | PRESTAR | RESERVAR | ADMINISTRAR; // 1111 = 15
// 3. Se comprueba con AND: si el bit esta activo, el resultado no es cero.
boolean martaAdmin = (permisosMarta & ADMINISTRAR) != 0; // false
boolean diegoAdmin = (permisosDiego & ADMINISTRAR) != 0; // false
boolean nuriaAdmin = (permisosNuria & ADMINISTRAR) != 0; // true
System.out.println("=== BiblioTech - Permisos de catalogo ===");
System.out.println("Marta Ruiz: " + permisosMarta
+ " (" + Integer.toBinaryString(permisosMarta) + ")"
+ " admin=" + martaAdmin);
System.out.println("Diego Alonso: " + permisosDiego
+ " (" + Integer.toBinaryString(permisosDiego) + ")"
+ " admin=" + diegoAdmin);
System.out.println("Nuria Vidal: " + permisosNuria
+ " (" + Integer.toBinaryString(permisosNuria) + ")"
+ " admin=" + nuriaAdmin);
// 4. Anadir un permiso con |= : enciende ese bit sin tocar los demas.
permisosMarta |= RESERVAR; // 0011 | 0100 = 0111 = 7
System.out.println();
System.out.println("Tras conceder RESERVAR a Marta Ruiz:");
System.out.println("Marta Ruiz: " + permisosMarta
+ " (" + Integer.toBinaryString(permisosMarta) + ")"
+ " puede reservar=" + ((permisosMarta & RESERVAR) != 0));
}
}Salida:
=== BiblioTech - Permisos de catalogo === Marta Ruiz: 3 (11) admin=false Diego Alonso: 7 (111) admin=false Nuria Vidal: 15 (1111) admin=true Tras conceder RESERVAR a Marta Ruiz: Marta Ruiz: 7 (111) puede reservar=true
Dos detalles que merecen atención:
- Los paréntesis en
(permisosMarta & ADMINISTRAR) != 0son obligatorios, no opcionales.&tiene menos precedencia que!=, así que sin ellos Java intentaría evaluarpermisosMarta & (ADMINISTRAR != 0), lo que ni siquiera compila por mezclarintyboolean. Integer.toBinaryStringno muestra los ceros a la izquierda: el 3 aparece como11, no como0011. Es normal; representa el mismo valor.
Conclusión
Ya dominas el catálogo completo de operadores de Java: la aritmética con su trampa de la división entera, el módulo y su pareja natural con /, la asignación compuesta con su cast implícito oculto, el incremento pre y post y cuándo la diferencia importa, las comparaciones que producen booleanos, los operadores lógicos con la evaluación de cortocircuito que te permite proteger expresiones peligrosas, el ternario para elegir valores sin if, las operaciones a nivel de bit aplicadas a banderas de permisos, la precedencia y el consejo de usar paréntesis, la diferencia crítica entre == y equals explicada por el pool de cadenas, y las limitaciones de precisión de double que un día te llevarán a BigDecimal. Con eso, BiblioTech ya sabe calcular una multa completa: días de retraso saturados a cero, recargo por gravedad y tope máximo.
En la siguiente lección, Entrada y Salida por Consola, resolveremos lo que ha quedado feo en estos ejemplos: dejaremos de escribir importes como 13.875 y aprenderemos a formatear la salida con printf (%.2f, anchos, alineación), y sobre todo dejaremos de tener los datos escritos a fuego en el código para pedírselos al usuario con Scanner. Es el último ingrediente antes de construir la primera versión completa de BiblioTechApp.
Curso de Programación en Java
Módulo 1: Introducción a Java
- Introducción a Java
- Configuración del Entorno de Desarrollo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Operadores
- Entrada y Salida por Consola
- Tu Primer Programa Completo: BiblioTech
Módulo 2: Flujo de Control
- Sentencias Condicionales
- Bucles
- Sentencias Switch
- Break y Continue
- Depuración y Trazas de Ejecución
- Proyecto: Menú Interactivo de BiblioTech
Módulo 3: Programación Orientada a Objetos
- Introducción a la POO
- Clases y Objetos
- Métodos
- Constructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
- La Clase Object: equals, hashCode y toString
Módulo 4: Programación Orientada a Objetos Avanzada
- Interfaces
- Clases Abstractas
- Clases Internas
- Clases Anónimas
- Expresiones Lambda
- Interfaces Funcionales y Referencias a Métodos
- Enumeraciones y Registros
Módulo 5: Estructuras de Datos y Colecciones
- Arreglos
- El Framework de Colecciones
- ArrayList
- LinkedList
- HashMap
- HashSet
- Cola y Deque
- Pila
- Ordenación y Búsqueda en Colecciones
Módulo 6: Manejo de Excepciones
- Introducción a las Excepciones
- Bloque Try-Catch
- Throw y Throws
- Excepciones Personalizadas
- Bloque Finally
- Try-with-resources y AutoCloseable
- Estrategias de Manejo de Errores y Logging
Módulo 7: Entrada/Salida de Archivos
- Lectura de Archivos
- Escritura de Archivos
- Flujos de Archivos
- BufferedReader y BufferedWriter
- Serialización
- La API NIO.2: Path y Files
- Formatos de Intercambio: CSV y Properties
Módulo 8: Multihilo y Concurrencia
- Introducción al Multihilo
- Creación de Hilos
- Ciclo de Vida de un Hilo
- Sincronización
- Utilidades de Concurrencia
- Colecciones Concurrentes y Variables Atómicas
- Tareas Asíncronas con CompletableFuture
Módulo 9: Redes
- Introducción a las Redes
- Sockets
- ServerSocket
- DatagramSocket y DatagramPacket
- URL y HttpURLConnection
- El Cliente HTTP Moderno
Módulo 10: Temas Avanzados
- Genéricos
- Anotaciones
- Reflexión
- Características de Java 8: Streams y Optional
- Fechas y Horas con java.time
- Java 9 y Más Allá
- Memoria, Recolección de Basura y Rendimiento
Módulo 11: Frameworks y Librerías de Java
- Introducción a los Frameworks de Java
- Spring Framework
- Hibernate
- JUnit
- Maven
- Pruebas Avanzadas con Mockito
- Librerías Esenciales del Ecosistema
