Domanda

Ho un loop che assomiglia a questo:

for (int i = 0; i < max; i++) {
    String myString = ...;
    float myNum = Float.parseFloat(myString);
    myFloats[i] = myNum;
}

Questo è il contenuto principale di un metodo il cui unico scopo è restituire l'array di float. Voglio che questo metodo restituisca null in caso di errore, quindi inserisco il ciclo all'interno di un blocco try ... catch , in questo modo:

try {
    for (int i = 0; i < max; i++) {
        String myString = ...;
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    }
} catch (NumberFormatException ex) {
    return null;
}

Ma poi ho anche pensato di inserire il blocco try ... catch all'interno del loop, in questo modo:

for (int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
    } catch (NumberFormatException ex) {
        return null;
    }
    myFloats[i] = myNum;
}

C'è qualche motivo, prestazione o altro, preferire l'uno all'altro?


Modifica: Il consenso sembra essere che sia più pulito inserire il loop all'interno del try / catch, possibilmente all'interno del proprio metodo. Tuttavia, c'è ancora dibattito su quale sia più veloce. Qualcuno può provare questo e tornare con una risposta unificata?

È stato utile?

Soluzione 3

Va ??bene, dopo Jeffrey L Whitledge ha detto che non c'erano differenze di prestazioni (dal 1997), sono andato a testarlo. Ho eseguito questo piccolo benchmark:

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

Ho controllato il codice byte risultante usando javap per assicurarmi che nulla fosse allineato.

I risultati hanno mostrato che, presupponendo insignificanti ottimizzazioni JIT, Jeffrey ha ragione ; non c'è assolutamente nessuna differenza di prestazioni su Java 6, VM client Sun (non avevo accesso ad altre versioni). La differenza di tempo totale è dell'ordine di alcuni millisecondi durante l'intero test.

Pertanto, l'unica considerazione è ciò che appare più pulito. Trovo che la seconda strada sia brutta, quindi mi atterrò alla prima o a La via di Ray Hayes .

Altri suggerimenti

PERFORMANCE:

Non c'è assolutamente alcuna differenza di prestazioni nel punto in cui sono posizionate le strutture try / catch. Internamente, vengono implementati come una tabella di intervalli di codice in una struttura creata quando viene chiamato il metodo. Mentre il metodo è in esecuzione, le strutture try / catch sono completamente fuori dal quadro a meno che non si verifichi un lancio, quindi la posizione dell'errore viene confrontata con la tabella.

Ecco un riferimento: http: // www. javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

La tabella è descritta a metà circa.

Prestazioni : come ha affermato Jeffrey nella sua risposta, in Java non rende molta differenza.

In generale , per la leggibilità del codice, la scelta di dove catturare l'eccezione dipende dal fatto che si desideri che l'elaborazione del ciclo continui o meno.

Nel tuo esempio sei tornato dopo aver preso un'eccezione. In tal caso, metterei il try / catch intorno al loop. Se vuoi semplicemente catturare un valore negativo ma continuare l'elaborazione, inseriscilo.

Il terzo modo : puoi sempre scrivere il tuo metodo ParseFloat statico e gestire l'eccezione in quel metodo anziché nel tuo ciclo. Rendere la gestione delle eccezioni isolata al loop stesso!

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

Mentre le prestazioni potrebbero essere le stesse e ciò che "sembra" meglio è molto soggettivo, c'è ancora una differenza abbastanza grande nella funzionalità. Prendi il seguente esempio:

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

Il ciclo while si trova all'interno del blocco try catch, la variabile 'j' viene incrementata fino a raggiungere 40, stampata quando j mod 4 è zero e viene generata un'eccezione quando j colpisce 20.

Prima di ogni dettaglio, ecco l'altro esempio:

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

Stessa logica di cui sopra, l'unica differenza è che il blocco try / catch è ora all'interno del ciclo while.

Ecco l'output (mentre in try / catch):

4
8
12 
16
in catch block

E l'altro output (try / catch in while):

4
8
12
16
in catch block
24
28
32
36
40

Lì hai una differenza abbastanza significativa:

mentre in try / catch esce dal ciclo

prova / intercetta mentre mantiene attivo il loop

Sono d'accordo con tutti i post sulle prestazioni e la leggibilità. Tuttavia, ci sono casi in cui conta davvero. Un paio di altre persone ne hanno parlato, ma potrebbe essere più facile vederlo con degli esempi.

Considera questo esempio leggermente modificato:

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

Se vuoi che il metodo parseAll () restituisca null se ci sono errori (come nell'esempio originale), dovresti mettere il try / catch all'esterno in questo modo:

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

In realtà, dovresti probabilmente restituire un errore qui invece di null, e in genere non mi piace avere più ritorni, ma hai l'idea.

D'altra parte, se vuoi che ignori i problemi e analizzi qualunque stringa possa fare, avresti messo il try / catch all'interno del loop in questo modo:

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

Come già accennato, le prestazioni sono le stesse. Tuttavia, l'esperienza dell'utente non è necessariamente identica. Nel primo caso, fallirai rapidamente (cioè dopo il primo errore), tuttavia se inserisci il blocco try / catch all'interno del ciclo, puoi catturare tutti gli errori che verrebbero creati per una determinata chiamata al metodo. Quando analizzi una matrice di valori da stringhe in cui ti aspetti alcuni errori di formattazione, ci sono sicuramente casi in cui ti piacerebbe essere in grado di presentare tutti gli errori all'utente in modo che non debbano provare a risolverli uno per uno .

Se fallisce tutto o niente, allora il primo formato ha senso. Se si desidera essere in grado di elaborare / restituire tutti gli elementi non in errore, è necessario utilizzare il secondo modulo. Questi sarebbero i miei criteri di base per scegliere tra i metodi. Personalmente, se fosse tutto o niente, non userei il secondo modulo.

Fintanto che si è consapevoli di ciò che è necessario eseguire nel ciclo, è possibile mettere la presa di prova al di fuori del ciclo. Ma è importante capire che il ciclo terminerà non appena si verifica l'eccezione e che potrebbe non essere sempre quello che desideri. Questo è in realtà un errore molto comune nel software basato su Java. Le persone devono elaborare una serie di elementi, come svuotare una coda, e fare affidamento su un'istruzione try / catch esterna che gestisce tutte le possibili eccezioni. Potrebbero anche gestire solo un'eccezione specifica all'interno del ciclo e non aspettarsi che si verifichino altre eccezioni. Quindi, se si verifica un'eccezione che non viene gestita all'interno del ciclo, il ciclo sarà "prevenuto", termina probabilmente prematuramente e l'istruzione catch esterna gestisce l'eccezione.

Se il ciclo avesse il suo ruolo nella vita di svuotare una coda, molto probabilmente quel ciclo potrebbe finire prima che quella coda fosse davvero svuotata. Errore molto comune.

Nei tuoi esempi non c'è differenza funzionale. Trovo il tuo primo esempio più leggibile.

Dovresti preferire la versione esterna alla versione interna. Questa è solo una versione specifica della regola, sposta qualsiasi cosa al di fuori del ciclo che puoi spostare al di fuori del ciclo. A seconda del compilatore IL e del compilatore JIT, le tue due versioni potrebbero o meno finire con caratteristiche prestazionali diverse.

In un'altra nota dovresti probabilmente guardare float. TryParse o Convert.ToFloat.

Se inserisci il try / catch all'interno del loop, continuerai a eseguire il loop dopo un'eccezione. Se lo metti fuori dal ciclo, ti fermerai non appena viene generata un'eccezione.

La mia prospettiva sarebbe che i blocchi try / catch siano necessari per assicurare una corretta gestione delle eccezioni, ma la creazione di tali blocchi ha implicazioni sulle prestazioni. Poiché i loop contengono calcoli ripetitivi intensivi, non è consigliabile inserire blocchi try / catch all'interno dei loop. Inoltre, sembra che si verifichi questa condizione, spesso è "eccezione". o " RuntimeException " che viene catturato. RuntimeException catturata nel codice dovrebbe essere evitata. Ancora una volta, se si lavora in una grande azienda, è essenziale registrare correttamente tale eccezione o arrestare l'eccezione di runtime. L'intero punto di questa descrizione è PER FAVORE EVITARE DI UTILIZZARE BLOCCHI TRY-CATCH IN LOOP

la configurazione di un frame stack speciale per il try / catch aggiunge un ulteriore sovraccarico, ma la JVM potrebbe essere in grado di rilevare il fatto che stai tornando e ottimizzarlo via.

a seconda del numero di iterazioni, la differenza di prestazioni sarà probabilmente trascurabile.

Comunque concordo con gli altri sul fatto che averlo al di fuori del loop rende il corpo del loop più pulito.

Se c'è la possibilità che tu voglia continuare con l'elaborazione piuttosto che uscire se c'è un numero non valido, allora vorresti che il codice fosse all'interno del ciclo.

Se è all'interno, otterrai l'overhead della struttura try / catch N volte, al contrario di una sola volta all'esterno.


Ogni volta che viene chiamata una struttura Try / Catch, si aggiunge un sovraccarico all'esecuzione del metodo. Solo un po 'di memoria e amp; le zecche del processore necessarie per gestire la struttura. Se stai eseguendo un loop 100 volte e, per amor di ipotesi, supponiamo che il costo sia di 1 tick per ogni chiamata try / catch, quindi avere il Try / Catch all'interno del loop ti costa 100 tick, invece di solo 1 tick se è al di fuori del ciclo.

Il punto centrale delle eccezioni è incoraggiare il primo stile: lasciare che la gestione degli errori sia consolidata e gestita una volta, non immediatamente in ogni possibile sito di errore.

mettilo dentro. È possibile continuare l'elaborazione (se lo si desidera) oppure generare un'utile eccezione che indica al client il valore di myString e l'indice dell'array contenente il valore errato. Penso che NumberFormatException ti dirà già il valore negativo, ma il principio è quello di inserire tutti i dati utili nelle eccezioni che hai lanciato. Pensa a cosa sarebbe interessante per te nel debugger a questo punto del programma.

Considera:

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

Nel momento del bisogno apprezzerai davvero un'eccezione come questa con quante più informazioni possibili.

Mi piacerebbe aggiungere il mio 0.02c su due considerazioni concorrenti quando guardo al problema generale di dove posizionare la gestione delle eccezioni:

  1. Il "più ampio" la responsabilità del blocco try-catch (ovvero all'esterno del ciclo nel tuo caso) significa che quando si modifica il codice in un momento successivo, è possibile aggiungere erroneamente una riga gestita dal tuo esistente cattura ; forse involontariamente. Nel tuo caso, questo è meno probabile perché stai catturando esplicitamente un NumberFormatException

  2. Il "più stretto" la responsabilità del blocco try-catch , più diventa difficile il refactoring. In particolare quando (come nel tuo caso) stai eseguendo un "non locale" istruzioni all'interno del blocco catch (l'istruzione return null ).

Dipende dalla gestione degli errori. Se vuoi solo saltare gli elementi di errore, prova dentro:

for(int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    } catch (NumberFormatException ex) {
        --i;
    }
}

In ogni altro caso preferirei provare fuori. Il codice è più leggibile, è più pulito. Forse sarebbe meglio lanciare un IllegalArgumentException nel caso dell'errore se restituisce null.

Inserirò i miei $ 0,02. A volte finisci con la necessità di aggiungere un " finalmente " più avanti nel tuo codice (perché chi mai scrive il loro codice perfettamente la prima volta?). In questi casi, improvvisamente ha più senso avere il tentativo / cattura al di fuori del ciclo. Ad esempio:

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

Perché se si verifica un errore o meno, si desidera rilasciare la connessione al database (o scegliere il tipo preferito di altra risorsa ...) una volta.

Un altro aspetto non menzionato in precedenza è il fatto che ogni tentativo di cattura ha un certo impatto sullo stack, che può avere implicazioni per i metodi ricorsivi.

Se il metodo " outer () " chiama il metodo " inner () " (che può chiamarsi ricorsivamente), prova a individuare il metodo try-catch nel metodo "esterno ()" se possibile. Un semplice "arresto anomalo dello stack" esempio che usiamo in una classe di performance fallisce a circa 6.400 frame quando il try-catch è nel metodo interno e a circa 11.600 quando è nel metodo esterno.

Nel mondo reale, questo può essere un problema se stai usando il modello Composito e hai strutture nidificate grandi e complesse.

Se vuoi catturare l'eccezione per ogni iterazione, o controllare a quale iterazione viene generata l'eccezione e catturare tutte le eccezioni in un itertaion, posiziona provare ... catch all'interno del loop. Questo non interromperà il ciclo se si verifica l'eccezione e puoi rilevare ogni eccezione in ogni iterazione durante il ciclo.

Se vuoi interrompere il loop ed esaminare l'eccezione ogni volta che viene lanciato, usa try ... catch out of the loop. Ciò interromperà il ciclo ed eseguirà le istruzioni dopo la cattura (se presenti).

Tutto dipende dalle tue necessità. Preferisco usare try ... catch all'interno del ciclo durante la distribuzione poiché, se si verifica Eccezione, i risultati non sono ambigui e il ciclo non si interromperà e verrà eseguito completamente.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top