¿Qué pasa si el usuario no escribe un número?
En ProductoController tenemos conversiones como esta:
double precio = Double.parseDouble(vista.getTxtPrecio());
Funciona si el usuario escribe 450 o 450.50. Pero imaginemos que escribe:
Java no puede inventar un número que no está ahí. La operación no puede continuar normalmente.
Una excepción interrumpe el flujo normal de una operación
Probemos un ejemplo mínimo, sin Swing:
public class Prueba {
public static void main(String[] args) {
String texto = "hola";
int numero = Integer.parseInt(texto);
System.out.println(numero);
}
}
El programa intenta convertir "hola" a int. Como no puede hacerlo, Java genera una excepción.
La excepción se propaga y la operación termina abruptamente. En una aplicación gráfica veremos el error en la consola y la acción iniciada por el usuario no se completa.
Podemos detectar esa excepción y decidir qué debe hacer la aplicación: informar, pedir otro dato, cancelar la operación, registrar el problema, etc.
El texto rojo también nos da información
Un mensaje típico podría comenzar así:
at java.base/java.lang.NumberFormatException.forInputString(...)
at java.base/java.lang.Integer.parseInt(...)
at Prueba.main(Prueba.java:4)
NumberFormatException: nos indica qué clase de problema ocurrió.For input string: "hola": aporta datos sobre la causa concreta.at muestran por dónde pasó la ejecución hasta llegar al problema. Ese recorrido se llama stack trace.try: intentá ejecutar este bloque
Si sabemos que una operación puede generar una excepción que queremos manejar, podemos colocarla dentro de un bloque try:
try {
int numero = Integer.parseInt(texto);
}
try no evita el problema ni hace desaparecer la excepción. Solo delimita un bloque cuyo resultado vamos a controlar.
try, necesitamos decir qué queremos hacer con esa excepción.catch: si ocurre esta excepción, ejecutá este bloque
try {
int numero = Integer.parseInt(texto);
System.out.println(numero);
} catch (NumberFormatException e) {
System.out.println("El dato ingresado no es un número válido.");
}
try → NumberFormatException → catch
catch no transforma "hola" en un número. Nos permite decidir cómo responder cuando la conversión no puede realizarse.¿Qué código deja de ejecutarse?
try {
int numero = Integer.parseInt(texto); // aquí ocurre la excepción
System.out.println("Conversión correcta"); // esta línea se salta
}
catch (NumberFormatException e) {
System.out.println("Dato inválido");
}
System.out.println("El programa continúa");
Cuando se produce la excepción, Java abandona el resto del bloque try, busca un catch compatible y continúa desde allí.
¿Qué significa catch (NumberFormatException e)?
Es el tipo de excepción que este catch sabe manejar.
Es una variable que referencia al objeto excepción que ocurrió. Podría llamarse de otra manera; e es una convención frecuente.
Ese objeto contiene información. Por ejemplo:
catch (NumberFormatException e) {
System.out.println(e.getMessage());
}
getMessage() devuelve el mensaje asociado a la excepción.
e) y podemos invocar métodos sobre ella.El Controller puede manejar errores de formato
Ahora el código que antes podía romper la acción queda protegido:
private void agregarProducto() {
try {
long codigo = Long.parseLong(vista.getTxtCodigo());
String nombre = vista.getTxtNombre();
double precio = Double.parseDouble(vista.getTxtPrecio());
int stock = Integer.parseInt(vista.getTxtStock());
// después continuaremos con la operación...
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(
vista,
"Código, precio y stock deben ser números válidos"
);
}
}
Acá la ubicación tiene sentido: el Controller está interpretando datos que llegan desde la interfaz.
| Entrada | Resultado |
|---|---|
"450" | La conversión puede continuar. |
"450.50" | Puede convertirse a double. |
"cuatrocientos" | Se produce NumberFormatException y el catch informa al usuario. |
campo vacío "" | También produce NumberFormatException al intentar convertirlo. |
-450, Java lo convierte perfectamente a double. Ahí tenemos otro tipo de problema.throw: nosotros decidimos que la operación no puede continuar
Supongamos que nuestra regla es: el precio debe ser mayor que cero.
double precio = -450;
Para Java ese valor es un double perfectamente válido. Pero para nuestra aplicación no debería aceptarse.
if (precio <= 0) {
throw new IllegalArgumentException(
"El precio debe ser mayor que cero"
);
}
Leamos la línea por partes:
Creamos un objeto excepción.
Representa que el método recibió un argumento que no acepta.
Lanzamos la excepción: la ejecución normal del método se interrumpe y el problema se comunica a quien llamó.
El Service puede lanzar; el Controller puede capturar
En ProductoService:
if (precio <= 0) {
throw new IllegalArgumentException(
"El precio debe ser mayor que cero"
);
}
El Service comunica: “no puedo realizar esta operación”.
En ProductoController:
try {
service.agregarProducto(codigo, nombre, precio, stock);
} catch (IllegalArgumentException e) {
JOptionPane.showMessageDialog(vista, e.getMessage());
}
El Controller decide: “¿cómo se lo explico al usuario?”.
¿Por qué no usar simplemente un if y return?
if (precio <= 0) {
return;
}La operación termina, pero quien llamó puede no saber qué ocurrió ni por qué.
if (precio <= 0) {
throw new IllegalArgumentException(
"Precio inválido"
);
}La operación termina y el motivo puede ser capturado y tratado en otro nivel.
Esto no significa que todo if deba convertirse en excepción. Los if sirven para tomar decisiones normales del programa. Una excepción tiene sentido cuando estamos diciendo: esta operación no puede continuar como fue solicitada.
Java tiene muchas excepciones; hoy necesitamos unas pocas ideas firmes
Las excepciones con las que estamos trabajando pertenecen a la familia de excepciones de ejecución. Más adelante veremos otras situaciones en las que Java obliga a declarar o manejar determinadas excepciones.
Existe un bloque que puede ejecutarse ocurra o no una excepción. Lo retomaremos cuando trabajemos con recursos externos.
Permite declarar que un método puede propagar determinadas excepciones. No necesitamos desarrollarlo todavía.
También podemos crear clases de excepción específicas del dominio. Lo dejaremos para cuando exista una necesidad real.
Error, destinada a fallos graves del entorno que normalmente una aplicación no intenta recuperar. En este material estamos trabajando con excepciones.Probamos, provocamos y manejamos
Escribí un programa que intente convertir un texto a int y maneje NumberFormatException.
Checkpoint: ante "hola", el programa muestra un mensaje propio y luego continúa.
Adaptá agregarProducto() para manejar errores de conversión de código, precio y stock. Después agregá una regla que lance IllegalArgumentException.
Checkpoint: podés explicar quién lanza y quién captura.
Agregá reglas para código, nombre, precio y stock. Diseñá mensajes útiles y evitá catch (Exception e) cuando conocés el tipo concreto que querés manejar.
Checkpoint: cada excepción tiene un motivo identificable.
Casos para probar
| Código | Nombre | Precio | Stock | ¿Qué debería ocurrir? |
|---|---|---|---|---|
| 101 | Batman | 450 | 3 | Operación válida. |
| abc | Batman | 450 | 3 | Error de formato al convertir código. |
| 101 | Batman | caro | 3 | Error de formato al convertir precio. |
| 101 | Batman | -450 | 3 | La conversión funciona; la regla de negocio debe rechazar el precio. |
| 101 | 450 | 3 | Regla de negocio: nombre vacío. |
throw y arquitectura se mezclan demasiado, volvemos primero a programas mínimos de consola con una sola conversión. Cuando try-catch esté firme, regresamos a Tienda de Cómics.No alcanza con reconocer la sintaxis
1. ¿Qué bloque decide qué hacer si ocurre una NumberFormatException?
2. Si Double.parseDouble("hola") está dentro de un try, ¿el try logra convertirlo correctamente?
3. El precio -500 es un double válido pero viola nuestra regla. ¿Quién debería detectar esa regla en la arquitectura que estamos construyendo?
4. ¿Qué palabra usamos cuando nuestro código crea y lanza explícitamente una excepción?
5. "tres" escrito en el campo stock provoca un problema de…
Una aplicación robusta no supone que todo saldrá bien
Delimita código cuya ejecución puede producir una excepción que queremos manejar.
Captura un tipo de excepción y nos permite decidir qué hacer ante ella.
Permite que nuestro propio código comunique que una operación no puede continuar.
Micro-entrega de documentación
Completá para Tienda de Cómics una tabla como esta:
| Situación | Quién la detecta | Tipo de problema | Respuesta esperada |
|---|---|---|---|
Precio = "abc" | Controller | Formato / conversión | Informar que se espera un número. |
Precio = -450 | Service | Regla de negocio | Rechazar la operación y comunicar el motivo. |
Stock = "tres" | Controller | Formato / conversión | Informar que se espera un número. |
| Código repetido | Service | Regla de negocio | Rechazar la operación. |
throw y catch. No describas solo la sintaxis: decí qué responsabilidad cumple cada uno.Puente: ahora ya podemos volver a ProductoService sin que throw e IllegalArgumentException aparezcan de la nada. Sabemos qué significan y cómo viaja el problema hasta quien debe manejarlo.
