propagacion - tipos de excepciones en java netbeans
¿Debería intentar... atrapar ir dentro o fuera de un bucle? (19)
Tengo un bucle que se ve así:
for (int i = 0; i < max; i++) {
String myString = ...;
float myNum = Float.parseFloat(myString);
myFloats[i] = myNum;
}
Este es el contenido principal de un método cuyo único propósito es devolver la matriz de flotadores. Quiero que este método devuelva null
si hay un error, así que puse el ciclo dentro de un bloque try...catch
, como este:
try {
for (int i = 0; i < max; i++) {
String myString = ...;
float myNum = Float.parseFloat(myString);
myFloats[i] = myNum;
}
} catch (NumberFormatException ex) {
return null;
}
Pero luego también pensé en poner el bloque try...catch
dentro del ciclo, así:
for (int i = 0; i < max; i++) {
String myString = ...;
try {
float myNum = Float.parseFloat(myString);
} catch (NumberFormatException ex) {
return null;
}
myFloats[i] = myNum;
}
¿Hay alguna razón, rendimiento o de otro tipo, para preferir uno sobre el otro?
Editar: El consenso parece ser que es más limpio poner el ciclo dentro del try / catch, posiblemente dentro de su propio método. Sin embargo, todavía hay debate sobre cuál es más rápido. ¿Alguien puede probar esto y regresar con una respuesta unificada?
ACTUACIÓN:
No hay absolutamente ninguna diferencia de rendimiento en donde se colocan las estructuras try / catch. Internamente, se implementan como una tabla de rango de código en una estructura que se crea cuando se llama al método. Mientras el método se está ejecutando, las estructuras try / catch están completamente fuera de la imagen, a menos que ocurra un lanzamiento, luego se compara la ubicación del error con la tabla.
Aquí hay una referencia: http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html
La tabla se describe aproximadamente a la mitad del camino.
Como ya se mencionó, el rendimiento es el mismo. Sin embargo, la experiencia del usuario no es necesariamente idéntica. En el primer caso, fallará rápidamente (es decir, después del primer error); sin embargo, si coloca el bloque try / catch dentro del ciclo, puede capturar todos los errores que se crearían para una determinada llamada al método. Al analizar una matriz de valores de cadenas donde espera algunos errores de formato, definitivamente hay casos en los que le gustaría poder presentar todos los errores al usuario para que no tenga que intentar solucionarlos uno por uno. .
De acuerdo, después de que Jeffrey L. Whitledge dijo que no había diferencia en el rendimiento (a partir de 1997), fui y lo probé. Ejecuté este pequeño punto de referencia:
public class Main {
private static final int NUM_TESTS = 100;
private static int ITERATIONS = 1000000;
// time counters
private static long inTime = 0L;
private static long aroundTime = 0L;
public static void main(String[] args) {
for (int i = 0; i < NUM_TESTS; i++) {
test();
ITERATIONS += 1; // so the tests don''t always return the same number
}
System.out.println("Inside loop: " + (inTime/1000000.0) + " ms.");
System.out.println("Around loop: " + (aroundTime/1000000.0) + " ms.");
}
public static void test() {
aroundTime += testAround();
inTime += testIn();
}
public static long testIn() {
long start = System.nanoTime();
Integer i = tryInLoop();
long ret = System.nanoTime() - start;
System.out.println(i); // don''t optimize it away
return ret;
}
public static long testAround() {
long start = System.nanoTime();
Integer i = tryAroundLoop();
long ret = System.nanoTime() - start;
System.out.println(i); // don''t optimize it away
return ret;
}
public static Integer tryInLoop() {
int count = 0;
for (int i = 0; i < ITERATIONS; i++) {
try {
count = Integer.parseInt(Integer.toString(count)) + 1;
} catch (NumberFormatException ex) {
return null;
}
}
return count;
}
public static Integer tryAroundLoop() {
int count = 0;
try {
for (int i = 0; i < ITERATIONS; i++) {
count = Integer.parseInt(Integer.toString(count)) + 1;
}
return count;
} catch (NumberFormatException ex) {
return null;
}
}
}
Comprobé el código de bytes resultante usando javap para asegurarme de que no hubiera nada en línea.
Los resultados mostraron que, suponiendo optimizaciones de JIT insignificantes, Jeffrey está en lo cierto ; no hay absolutamente ninguna diferencia de rendimiento en Java 6, VM del cliente Sun (no tuve acceso a otras versiones). La diferencia de tiempo total es del orden de unos pocos milisegundos en toda la prueba.
Por lo tanto, la única consideración es lo que parece más limpio. Encuentro que la segunda forma es fea, así que me mantendré en la primera o en el camino de Ray Hayes .
Deberías preferir la versión externa sobre la versión interna. Esta es solo una versión específica de la regla, mueve cualquier cosa fuera del ciclo que puedas mover fuera del ciclo. Según el compilador de IL y el compilador JIT, sus dos versiones pueden o no tener características de rendimiento diferentes.
En otra nota, probablemente deberías mirar float.TryParse o Convert.ToFloat.
El objetivo principal de las excepciones es alentar el primer estilo: permitir que el manejo de errores se consolide y maneje una vez, no inmediatamente en cada sitio de error posible.
En sus ejemplos, no hay diferencia funcional. Encuentro tu primer ejemplo más legible.
Eso depende del manejo de fallas. Si solo quieres saltearte los elementos de error, prueba adentro:
for(int i = 0; i < max; i++) {
String myString = ...;
try {
float myNum = Float.parseFloat(myString);
myFloats[i] = myNum;
} catch (NumberFormatException ex) {
--i;
}
}
En cualquier otro caso, preferiría probar fuera. El código es más legible, es más limpio. Tal vez sería mejor lanzar una IllegalArgumentException en el caso de error en lugar de devolver null.
Estoy de acuerdo con todas las publicaciones de rendimiento y legibilidad. Sin embargo, hay casos en los que realmente importa. Otras personas mencionaron esto, pero podría ser más fácil verlo con ejemplos.
Considere este ejemplo ligeramente modificado:
public static void main(String[] args) {
String[] myNumberStrings = new String[] {"1.2345", "asdf", "2.3456"};
ArrayList asNumbers = parseAll(myNumberStrings);
}
public static ArrayList parseAll(String[] numberStrings){
ArrayList myFloats = new ArrayList();
for(int i = 0; i < numberStrings.length; i++){
myFloats.add(new Float(numberStrings[i]));
}
return myFloats;
}
Si desea que el método parseAll () devuelva nulo si hay algún error (como el ejemplo original), pondría el try / catch en el exterior así:
public static ArrayList parseAll1(String[] numberStrings){
ArrayList myFloats = new ArrayList();
try{
for(int i = 0; i < numberStrings.length; i++){
myFloats.add(new Float(numberStrings[i]));
}
} catch (NumberFormatException nfe){
//fail on any error
return null;
}
return myFloats;
}
En realidad, probablemente deberías devolver un error aquí en lugar de nulo, y generalmente no me gusta tener múltiples devoluciones, pero entiendes la idea.
Por otro lado, si quieres que simplemente ignore los problemas y analice las cadenas que pueda, pondrás el try / catch en el interior del ciclo de esta manera:
public static ArrayList parseAll2(String[] numberStrings){
ArrayList myFloats = new ArrayList();
for(int i = 0; i < numberStrings.length; i++){
try{
myFloats.add(new Float(numberStrings[i]));
} catch (NumberFormatException nfe){
//don''t add just this one
}
}
return myFloats;
}
Me gustaría agregar mi propio 0.02c
sobre dos consideraciones que compiten al mirar el problema general de dónde 0.02c
manejo de excepciones:
Cuanto más "amplia" sea la responsabilidad del bloque
try-catch
(es decir, fuera del bucle en su caso) significa que al cambiar el código en algún momento posterior, puede agregar erróneamente una línea manejada por su bloquecatch
existente; posiblemente involuntariamente. En su caso, esto es menos probable porque está capturando explícitamente unaNumberFormatException
Cuanto más "estrecha" sea la responsabilidad del bloque
try-catch
, más difícil será refactorizar. Particularmente cuando (como en su caso) está ejecutando una instrucción "no local" desde dentro del bloquecatch
(lareturn null
).
Otro aspecto no mencionado en lo anterior es el hecho de que cada try-catch tiene algún impacto en la pila, lo que puede tener implicaciones para los métodos recursivos.
Si el método "outer ()" llama al método "inner ()" (que puede llamarse recursivamente), trate de ubicar el try-catch en el método "outer ()" si es posible. Un simple ejemplo de "stack crash" que usamos en una clase de rendimiento falla a aproximadamente 6.400 cuadros cuando el try-catch está en el método interno, y en aproximadamente 11.600 cuando está en el método externo.
En el mundo real, esto puede ser un problema si está utilizando el patrón Compuesto y tiene estructuras anidadas grandes y complejas.
Pondré mis $ 0.02. A veces terminas necesitando agregar un "finalmente" más adelante en tu código (porque ¿quién escribe su código perfectamente la primera vez?). En esos casos, de repente tiene más sentido tener la prueba / captura fuera del ciclo. Por ejemplo:
try {
for(int i = 0; i < max; i++) {
String myString = ...;
float myNum = Float.parseFloat(myString);
dbConnection.update("MY_FLOATS","INDEX",i,"VALUE",myNum);
}
} catch (NumberFormatException ex) {
return null;
} finally {
dbConnection.release(); // Always release DB connection, even if transaction fails.
}
Porque si obtiene un error, o no, solo desea liberar su conexión de base de datos (o elegir su tipo preferido de otro recurso ...) una vez.
Si bien el rendimiento puede ser el mismo y lo que "se ve mejor" es muy subjetivo, todavía hay una gran diferencia en la funcionalidad. Toma el siguiente ejemplo:
Integer j = 0;
try {
while (true) {
++j;
if (j == 20) { throw new Exception(); }
if (j%4 == 0) { System.out.println(j); }
if (j == 40) { break; }
}
} catch (Exception e) {
System.out.println("in catch block");
}
El ciclo while está dentro del bloque try catch, la variable ''j'' se incrementa hasta que llega a 40, se imprime cuando j mod 4 es cero y se lanza una excepción cuando j toca 20.
Antes de cualquier detalle, aquí el otro ejemplo:
Integer i = 0;
while (true) {
try {
++i;
if (i == 20) { throw new Exception(); }
if (i%4 == 0) { System.out.println(i); }
if (i == 40) { break; }
} catch (Exception e) { System.out.println("in catch block"); }
}
La misma lógica que la anterior, la única diferencia es que el bloque try / catch está ahora dentro del ciclo while.
Aquí viene la salida (mientras está en try / catch):
4
8
12
16
in catch block
Y la otra salida (try / catch in while):
4
8
12
16
in catch block
24
28
32
36
40
Ahí tienes una diferencia bastante significativa:
mientras que en try / catch se rompe el ciclo
intenta / atrapa mientras mantiene el bucle activo
Si es un fracaso de todo o nada, entonces el primer formato tiene sentido. Si desea poder procesar / devolver todos los elementos que no fallan, debe usar el segundo formulario. Esos serían mis criterios básicos para elegir entre los métodos. Personalmente, si es todo o nada, no usaría la segunda forma.
Si está dentro, ganarás la sobrecarga de la estructura try / catch N veces, en lugar de solo una vez en el exterior.
Cada vez que se llama a una estructura Try / Catch, agrega sobrecarga a la ejecución del método. Solo los pequeños tics de memoria y procesador necesarios para manejar la estructura. Si está ejecutando un ciclo 100 veces, y por un hipotético motivo, digamos que el costo es de 1 tick por try / catch call, entonces tener Try / Catch dentro del ciclo le cuesta 100 tics, en lugar de solo 1 tick si es fuera del circuito.
Si pones el try / catch dentro del loop, seguirás repitiendo después de una excepción. Si lo coloca fuera del ciclo, se detendrá tan pronto como se produzca una excepción.
Siempre que sepa lo que necesita lograr en el ciclo, podría poner la captura de prueba fuera del ciclo. Pero es importante comprender que el ciclo terminará tan pronto como se produzca la excepción y que puede no ser siempre lo que usted desea. Este es un error muy común en el software basado en Java. Las personas necesitan procesar una serie de elementos, como vaciar una cola, y confiar falsamente en una declaración try / catch externa que maneje todas las excepciones posibles. También podrían manejar solo una excepción específica dentro del ciclo y no esperar que ocurra ninguna otra excepción. Luego, si ocurre una excepción que no se maneja dentro del ciclo, entonces el ciclo será "preemidado", posiblemente finalice prematuramente y la declaración de captura externa maneje la excepción.
Si el ciclo tiene como función en la vida vaciar una cola, es muy probable que ese ciclo termine antes de que la cola realmente se vacíe. Falla muy común.
la configuración de un marco de pila especial para el try / catch agrega una sobrecarga adicional, pero la JVM puede detectar el hecho de que usted está regresando y optimizar esto.
dependiendo del número de iteraciones, la diferencia de rendimiento probablemente será insignificante.
Sin embargo, estoy de acuerdo con los demás que tenerlo fuera del circuito hace que el cuerpo del bucle se vea más limpio.
Si existe la posibilidad de que alguna vez quiera continuar con el procesamiento en lugar de salir si hay un número no válido, entonces le gustaría que el código esté dentro del ciclo.
ponlo dentro. Puede seguir procesando (si lo desea) o puede lanzar una excepción útil que le indique al cliente el valor de myString y el índice de la matriz que contiene el valor incorrecto. Creo que NumberFormatException ya le dirá el valor incorrecto, pero el principio es colocar todos los datos útiles en las excepciones que arroja. Piensa en lo que sería interesante para ti en el depurador en este punto del programa.
Considerar:
try {
// parse
} catch (NumberFormatException nfe){
throw new RuntimeException("Could not parse as a Float: [" + myString +
"] found at index: " + i, nfe);
}
En el momento de la necesidad, realmente apreciará una excepción como esta con tanta información como sea posible.
Rendimiento : como dijo Jeffrey en su respuesta, en Java no hace mucha diferencia.
Generalmente , para la legibilidad del código, su elección de dónde atrapar la excepción depende de si desea que el ciclo continúe o no procesándose.
En su ejemplo, regresó al detectar una excepción. En ese caso, pondría el try / catch alrededor del ciclo. Si simplemente quiere tomar un valor malo pero seguir procesando, póngalo dentro.
La tercera forma : siempre puedes escribir tu propio método ParseFloat estático y hacer que se maneje la excepción en ese método en lugar de tu ciclo. ¡Haciendo que el manejo de excepciones esté aislado del bucle mismo!
class Parsing
{
public static Float MyParseFloat(string inputValue)
{
try
{
return Float.parseFloat(inputValue);
}
catch ( NumberFormatException e )
{
return null;
}
}
// .... your code
for(int i = 0; i < max; i++)
{
String myString = ...;
Float myNum = Parsing.MyParseFloat(myString);
if ( myNum == null ) return;
myFloats[i] = (float) myNum;
}
}