Pregunta

Tengo un bucle que se parece a esto:

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 coloco el bucle 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 también pensé en poner el bloque try ... catch dentro del bucle, de esta manera:

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 no, para preferir una sobre la otra?


Editar: El consenso parece ser que es más limpio poner el bucle 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?

¿Fue útil?

Solución 3

De acuerdo, después de Jeffrey L Whitledge dijo que no había diferencia de rendimiento (a partir de 1997), fui y lo probé. Me encontré con 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;
        }
    }
}

Verifiqué el bytecode resultante usando javap para asegurarme de que no se insertara nada.

Los resultados mostraron que, suponiendo optimizaciones de JIT insignificantes, Jeffrey tiene razón ; no hay absolutamente diferencia de rendimiento en Java 6, VM cliente de Sun (no tuve acceso a otras versiones). La diferencia de tiempo total es del orden de unos pocos milisegundos durante toda la prueba.

Por lo tanto, la única consideración es lo que parece más limpio. Me parece que la segunda forma es fea, por lo que me limitaré a la primera o a la manera de Ray Hayes .

Otros consejos

RENDIMIENTO:

No hay absolutamente ninguna diferencia de rendimiento en el lugar 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 se produzca un lanzamiento, luego la ubicación del error se compara con la tabla.

Aquí hay una referencia: http: // www. javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

La tabla se describe a mitad de camino hacia abajo.

Rendimiento : como Jeffrey dijo en su respuesta, en Java no tiene mucha diferencia.

En general , para facilitar la lectura del código, su elección de dónde detectar la excepción depende de si desea que el ciclo continúe procesándose o no.

En su ejemplo, regresó al detectar una excepción. En ese caso, pondría el try / catch alrededor del bucle. Si simplemente desea capturar un valor incorrecto pero continuar con el procesamiento, póngalo dentro.

La tercera forma : siempre puede escribir su propio método estático ParseFloat y hacer que se maneje la excepción en ese método en lugar de en su bucle. ¡Haciendo el manejo de la excepción aislado al propio bucle!

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;
    }
}

Si bien el rendimiento podría ser el mismo y qué aspecto tiene " " Mejor es muy subjetivo, todavía hay una diferencia bastante grande en la funcionalidad. Tomemos 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 bucle while está dentro del bloque try catch, la variable 'j' se incrementa hasta que alcanza 40, se imprime cuando j mod 4 es cero y se lanza una excepción cuando j golpea 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 ahora está dentro del bucle 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 está en try / catch se sale del bucle

probar / capturar mientras mantiene el bucle activo

Estoy de acuerdo con todas las publicaciones de rendimiento y legibilidad. Sin embargo, hay casos en los que realmente importa. Algunas otras personas mencionaron esto, pero podría ser más fácil de ver 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 de esta manera:

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 en general no me gusta tener múltiples devoluciones, pero entiendes la idea.

Por otro lado, si solo quieres ignorar los problemas y analizar las cadenas que puedas, deberías poner el try / catch en el interior del bucle 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;
}

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ápido (es decir, después del primer error), sin embargo, si coloca el bloque try / catch dentro del bucle, puede capturar todos los errores que se crearían para una llamada dada al método. Al analizar una serie de valores de cadenas en las que espera algunos errores de formato, definitivamente hay casos en los que le gustaría poder presentar todos los errores al usuario para que no tengan que intentar solucionarlos uno por uno. .

Si es un error de todo o nada, entonces el primer formato tiene sentido. Si desea poder procesar / devolver todos los elementos que no fallan, necesita 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 el segundo formulario.

Mientras esté al tanto de lo que necesita lograr en el bucle, puede poner el catch catch fuera del bucle. Pero es importante comprender que el bucle terminará tan pronto como se produzca la excepción y puede que no siempre sea lo que desea. Esto es realmente un error muy común en el software basado en Java. Las personas deben procesar una serie de elementos, como vaciar una cola, y confiar falsamente en una declaración externa de prueba / captura que maneje todas las excepciones posibles. También podrían estar manejando solo una excepción específica dentro del bucle y no esperar que ocurra ninguna otra excepción. Luego, si ocurre una excepción que no se maneja dentro del bucle, entonces el bucle será "precortado", que posiblemente termine prematuramente y la declaración de captura externa maneja la excepción.

Si el bucle tenía como función en la vida vaciar una cola, es muy probable que ese bucle pueda terminar antes de que la cola se vacíe realmente. Falla muy común.

En tus ejemplos no hay diferencia funcional. Encuentro tu primer ejemplo más legible.

Debería preferir la versión exterior a la versión interna. Esta es solo una versión específica de la regla, mueva cualquier cosa fuera del bucle que pueda mover fuera del bucle. Dependiendo del compilador IL y el compilador JIT, sus dos versiones pueden o no tener diferentes características de rendimiento.

En otra nota, probablemente deberías mirar float.TryParse o Convert.ToFloat.

Si coloca el try / catch dentro del bucle, seguirá haciendo un bucle después de una excepción. Si lo coloca fuera del bucle, se detendrá tan pronto como se lance una excepción.

Mi perspectiva sería que los bloques try / catch son necesarios para asegurar el manejo adecuado de las excepciones, pero la creación de dichos bloques tiene implicaciones de rendimiento. Dado que, los bucles contienen cálculos repetitivos intensivos, no se recomienda colocar los bloques try / catch dentro de los bucles. Además, parece que donde ocurre esta condición, a menudo es " Excepción " o " RuntimeException " que es atrapado Debe evitarse RuntimeException ser atrapado en el código. Nuevamente, si trabaja en una gran empresa, es esencial registrar esa excepción correctamente o evitar que se produzca una excepción en tiempo de ejecución. Todo el punto de esta descripción es POR FAVOR EVITE UTILIZAR LOS BLOQUES TRY-CATCH EN LOOPS

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 está regresando y optimizar esto.

dependiendo de la cantidad de iteraciones, la diferencia de rendimiento probablemente será insignificante.

Sin embargo, estoy de acuerdo con los demás en que tenerlo fuera del bucle hace que el cuerpo del bucle se vea más limpio.

Si existe la posibilidad de que desees continuar con el procesamiento en lugar de salir si hay un número no válido, querrás que el código esté dentro del bucle.

Si está dentro, obtendrás la sobrecarga de la estructura try / catch N veces, en lugar de solo una vez en el exterior.


Cada vez que se llama una estructura Try / Catch, se agrega una sobrecarga a la ejecución del método. Solo un poco de memoria & amp; Procesador de ticks necesarios para lidiar con la estructura. Si está ejecutando un bucle 100 veces, y por razones hipotéticas, digamos que el costo es de 1 tick por llamada try / catch, luego tener el Try / Catch dentro del loop le cuesta 100 ticks, en lugar de solo 1 tick si es fuera del bucle.

El punto central de las excepciones es fomentar el primer estilo: permitir que el manejo de errores se consolide y maneje una vez, no inmediatamente en cada sitio de error posible.

ponlo dentro. Puede seguir procesando (si lo desea) o puede lanzar una excepción útil que le diga al cliente el valor de myString y el índice de la matriz que contiene el valor incorrecto. Creo que la excepción NumberFormatException ya te dirá el valor incorrecto, pero el principio es colocar todos los datos útiles en las excepciones que lances. Piense en lo que sería interesante para usted en el depurador en este punto del programa.

Considera:

try {
   // parse
} catch (NumberFormatException nfe){
   throw new RuntimeException("Could not parse as a Float: [" + myString + 
                              "] found at index: " + i, nfe);
} 

En el momento de necesidad, realmente apreciarás una excepción como esta con tanta información como sea posible.

Me gustaría agregar mi propio 0.02c sobre dos consideraciones en competencia cuando analizo el problema general de dónde colocar el manejo de excepciones:

  1. El " más ancho " 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 por error una línea que es manejada por su existente bloque de captura ; posiblemente sin intención. En su caso, esto es menos probable porque está capturando explícitamente un NumberFormatException

  2. El " más estrecho " La responsabilidad del bloque try-catch hace que la refactorización sea más difícil. En particular, cuando (como en su caso) está ejecutando un " no local " instrucción desde dentro del bloque catch (la declaración return null ).

Eso depende del manejo de fallas. Si solo desea omitir los elementos de error, intente dentro:

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 el intento fuera. El código es más legible, está más limpio. Tal vez sería mejor lanzar una excepción IllegalArgumentException en el caso de error en lugar de devolver nulo.

Pondré mi $ 0.02 pulg. A veces terminas necesitando agregar un " finalmente " más adelante en su código (porque ¿quién escribe su código a la perfección la primera vez?). En esos casos, de repente tiene más sentido tener el try / catch fuera del bucle. 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 recibe un error o no, solo desea liberar su conexión de base de datos (o seleccionar su tipo favorito de otro recurso ...) una vez.

Otro aspecto que no se menciona anteriormente es el hecho de que cada intento de captura tiene cierto impacto en la pila, lo que puede tener implicaciones para los métodos recursivos.

Si el método " exterior () " llama al método " inner () " (que puede llamarse a sí mismo de forma recursiva), intente localizar el try-catch en el método " outer () " si es posible. Un simple "choque de pila" el ejemplo que usamos en una clase de rendimiento falla en 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ás usando el patrón Compuesto y tienes estructuras anidadas grandes y complejas.

Si desea capturar la Excepción para cada iteración, o comprobar en qué iteración se lanza la Excepción y capturar todas las Excepciones en una iteración, intente ... atrape dentro del bucle. Esto no interrumpirá el bucle si se produce una excepción y puede capturar cada excepción en cada iteración a lo largo del bucle.

Si desea romper el bucle y examinar la Excepción cada vez que se lanza, use try ... catch out of the loop. Esto romperá el bucle y ejecutará las instrucciones después de la captura (si las hay).

Todo depende de tu necesidad. Prefiero usar try ... catch dentro del bucle mientras se implementa ya que, si se produce una excepción, los resultados no son ambiguos y el bucle no se romperá y se ejecutará por completo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top