Question

J'ai une boucle qui ressemble à ceci:

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

Il s'agit du contenu principal d'une méthode dont le seul but est de renvoyer le tableau de floats. Je souhaite que cette méthode retourne null en cas d'erreur, alors je mets la boucle dans un bloc try ... catch , comme ceci:

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

Mais ensuite, j'ai aussi pensé à insérer le bloc try ... catch dans la boucle, comme ceci:

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

Existe-t-il une raison, de performance ou autre, de préférer l’une à l’autre?

Modifier: Le consensus semble être qu'il est préférable de placer la boucle à l'intérieur du try / catch, éventuellement à l'intérieur de sa propre méthode. Cependant, il y a toujours un débat sur ce qui est plus rapide. Quelqu'un peut-il tester cela et revenir avec une réponse unifiée?

Était-ce utile?

La solution 3

Très bien, après , a déclaré Jeffrey L. Whitledge qu’il n’y avait pas de différence de performance (à partir de 1997), je suis allé le tester. J'ai couru ce petit repère:

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

J'ai vérifié le bytecode résultant à l'aide de javap pour m'assurer que rien n'était en ligne.

Les résultats ont montré que, en supposant des optimisations JIT non significatives, Jeffrey est correct ; il n'y a absolument aucune différence de performances sur Java 6, machine virtuelle client Sun (je n'avais pas accès aux autres versions). La différence de temps totale est de l'ordre de quelques millisecondes sur l'ensemble du test.

Par conséquent, la seule considération est ce qui semble le plus propre. Je trouve que la deuxième façon est laide, je vais donc m'en tenir à la première ou à la La voie de Ray Hayes .

Autres conseils

PERFORMANCE:

Il n’existe absolument aucune différence de performances quant à l’emplacement des structures try / catch. En interne, ils sont implémentés sous la forme d'une table de plages de codes dans une structure créée lors de l'appel de la méthode. Pendant que la méthode est en cours d'exécution, les structures try / catch sont complètement hors de propos, sauf si une projection survient, puis l'emplacement de l'erreur est comparé à la table.

Voici une référence: http: // www. javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

Le tableau est décrit à peu près à mi-chemin.

Performances : comme Jeffrey dans sa réponse, en Java, cela ne fait pas beaucoup de différence.

En règle générale , pour que le code soit lisible, le choix de l'endroit où intercepter l'exception dépend de la décision de conserver ou non le traitement de la boucle.

Dans votre exemple, vous êtes revenu après avoir intercepté une exception. Dans ce cas, je mettrais le try / catch autour de la boucle. Si vous voulez simplement attraper une mauvaise valeur mais poursuivre le traitement, mettez-le à l'intérieur.

La troisième façon : vous pouvez toujours écrire votre propre méthode ParseFloat statique et faire en sorte que la gestion des exceptions soit traitée dans cette méthode plutôt que dans votre boucle. Rendre la gestion des exceptions isolée de la boucle elle-même!

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

Les performances peuvent être identiques et l'apparence de " " mieux est très subjectif, il y a toujours une très grande différence dans les fonctionnalités. Prenons l'exemple suivant:

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

La boucle while est à l'intérieur du bloc try catch, la variable 'j' est incrémentée jusqu'à atteindre 40, imprimée lorsque j mod 4 vaut zéro et une exception est levée lorsque j atteint 20.

Avant tout détail, voici l'autre exemple:

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

Même logique que ci-dessus, la seule différence est que le bloc try / catch est maintenant dans la boucle while.

Voici la sortie (lors de try / catch):

4
8
12 
16
in catch block

Et l'autre sortie (try / catch in while):

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

Vous avez là une différence assez significative:

pendant que vous essayez / attrapez les ruptures de la boucle

try / catch in pendant que la boucle est active

Je suis d’accord avec tous les articles sur la performance et la lisibilité. Cependant, il y a des cas où cela compte vraiment. Quelques personnes ont mentionné cela, mais il serait peut-être plus facile de voir à l'aide d'exemples.

Considérez cet exemple légèrement modifié:

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 vous souhaitez que la méthode parseAll () renvoie la valeur null s'il y a des erreurs (comme dans l'exemple d'origine), vous devez placer try / catch à l'extérieur comme suit:

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 réalité, vous devriez probablement renvoyer une erreur ici au lieu de null, et généralement je n'aime pas avoir plusieurs déclarations, mais vous en avez l'idée.

D'un autre côté, si vous voulez qu'il ignore les problèmes et analyse toutes les chaînes qu'il peut, vous pouvez placer le paramètre try / catch à l'intérieur de la boucle comme suit:

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

Comme déjà mentionné, la performance est la même. Cependant, l'expérience utilisateur n'est pas nécessairement identique. Dans le premier cas, vous échouerez rapidement (c'est-à-dire après la première erreur). Cependant, si vous placez le bloc try / catch dans la boucle, vous pouvez capturer toutes les erreurs qui seraient créées pour un appel donné à la méthode. Lors de l'analyse d'un tableau de valeurs à partir de chaînes contenant des erreurs de formatage, il est tout à fait possible que vous souhaitiez pouvoir présenter toutes les erreurs à l'utilisateur afin qu'il n'ait pas besoin d'essayer de les corriger une par une. .

Si le tout ou rien échoue, le premier format prend tout son sens. Si vous voulez pouvoir traiter / renvoyer tous les éléments non défaillants, vous devez utiliser le second formulaire. Ce sont mes critères de base pour choisir entre les méthodes. Personnellement, si c'est tout ou rien, je n'utiliserais pas le deuxième formulaire.

Tant que vous êtes conscient de ce que vous devez accomplir dans la boucle, vous pouvez placer la capture d’essai en dehors de la boucle. Mais il est important de comprendre que la boucle se terminera dès que l'exception se produira et que ce ne sera pas toujours ce que vous voudrez. C'est en fait une erreur très courante dans les logiciels basés sur Java. Les utilisateurs doivent traiter un certain nombre d'éléments, par exemple vider une file d'attente, et s'appuyer à tort sur une instruction externe try / catch gérant toutes les exceptions possibles. Ils pourraient également ne gérer qu'une exception spécifique à l'intérieur de la boucle et ne pas s'attendre à ce qu'une autre exception se produise. Ensuite, si une exception survient qui n’est pas gérée à l’intérieur de la boucle, la boucle sera "pré-introduite", elle se termine éventuellement prématurément et l’instruction catch externe gère l’exception.

Si la boucle avait pour rôle dans la vie de vider une file d'attente, cette boucle pourrait très probablement se terminer avant que cette file d'attente ne soit réellement vidée. Faute très commune.

Dans vos exemples, il n'y a pas de différence fonctionnelle. Je trouve votre premier exemple plus lisible.

Vous devriez préférer la version externe à la version interne. Ceci est juste une version spécifique de la règle, déplacez tout ce qui est en dehors de la boucle et que vous pouvez déplacer en dehors de la boucle. En fonction du compilateur IL et du compilateur JIT, vos deux versions peuvent présenter ou non des caractéristiques de performance différentes.

Sur une autre note, vous devriez probablement regarder float.TryParse ou Convert.ToFloat.

Si vous placez try / catch dans la boucle, vous continuerez à boucler après une exception. Si vous le mettez en dehors de la boucle, vous vous arrêtez dès qu'une exception est levée.

Mon point de vue serait que les blocs try / catch sont nécessaires pour assurer une gestion correcte des exceptions, mais la création de tels blocs a des conséquences sur les performances. Comme les boucles contiennent des calculs répétitifs intensifs, il est déconseillé de placer des blocs try / catch dans les boucles. En outre, il semble que là où cette condition survient, il est souvent "Exception". ou " RuntimeException " qui est attrapé. Il faut éviter que RuntimeException soit pris dans le code. Encore une fois, si vous travaillez dans une grande entreprise, il est essentiel de consigner cette exception correctement ou d'empêcher qu'elle ne se produise. Le point entier de cette description est VEUILLEZ ÉVITER L’UTILISATION DE BLOCS ESSAYER DE PRENDRE DES PISTES DANS DES BOUCLES

La configuration d'un cadre de pile spécial pour try / catch ajoute un temps système supplémentaire, mais la machine virtuelle Java peut être en mesure de détecter le retour et d'optimiser le retour.

En fonction du nombre d'itérations, la différence de performances sera probablement négligeable.

Cependant, je suis d'accord avec les autres sur le fait que le fait de le placer hors de la boucle donne un aspect plus net au corps de la boucle.

S'il est possible que vous souhaitiez poursuivre le traitement plutôt que de quitter s'il existe un nombre non valide, vous voudriez que le code soit dans la boucle.

Si c'est à l'intérieur, vous gagnerez le temps système de la structure try / catch N fois, par opposition à une seule fois à l'extérieur.

Chaque fois qu'une structure Try / Catch est appelée, elle ajoute un temps système à l'exécution de la méthode. Juste le peu de mémoire & amp; tiques de processeur nécessaires pour traiter avec la structure. Si vous exécutez une boucle 100 fois, et par hypothèse, supposons que le coût est de 1 tick par appel try / catch, alors le test Try / Catch dans la boucle vous coûte 100 ticks, au lieu de 1 tick si en dehors de la boucle.

Le point essentiel des exceptions est d'encourager le premier style: laisser le traitement des erreurs être consolidé et traité une fois, pas immédiatement sur tous les sites d'erreur possibles.

le mettre à l'intérieur. Vous pouvez continuer le traitement (si vous le souhaitez) ou vous pouvez générer une exception utile qui indique au client la valeur de myString et l'index du tableau contenant la valeur incorrecte. Je pense que NumberFormatException vous indiquera déjà la mauvaise valeur, mais le principe est de placer toutes les données utiles dans les exceptions que vous lancez. Pensez à ce qui pourrait vous intéresser dans le débogueur à ce stade du programme.

Considérez:

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

En cas de besoin, vous apprécierez vraiment une exception comme celle-ci, qui contient autant d'informations que possible.

J'aime ajouter mon propre 0.02c à propos de deux considérations contradictoires lorsque l'on examine le problème général de savoir où placer la gestion des exceptions:

  1. Le " plus large " la responsabilité du bloc try-catch (c'est-à-dire en dehors de la boucle dans votre cas) signifie que lorsque vous modifiez le code ultérieurement, vous pouvez ajouter par erreur une ligne gérée par votre existant. catch block; peut-être involontairement. Dans votre cas, cela est moins probable car vous attrapez explicitement une NumberFormatException

  2. Le " plus étroit " la responsabilité du bloc try-catch , le refactoring devient plus difficile. En particulier lorsque (comme dans votre cas) vous exécutez une opération "non locale". instruction à l'intérieur du bloc catch (l'instruction return null ).

Cela dépend de la gestion des échecs. Si vous voulez juste ignorer les éléments d'erreur, essayez à l'intérieur:

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

Dans tous les autres cas, je préférerais essayer à l’extérieur. Le code est plus lisible, plus propre. Peut-être serait-il préférable de créer une exception IllegalArgumentException dans le cas d'erreur si la valeur null est renvoyée.

Je vais mettre mes 0,02 $. Parfois, vous finissez par avoir besoin d'ajouter un "enfin". plus tard dans votre code (car qui a déjà écrit son code parfaitement la première fois?). Dans ces cas, il est tout à fait logique d’avoir l’essai / attrape en dehors de la boucle. Par exemple:

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

Parce que si vous obtenez une erreur ou non, vous souhaitez uniquement libérer votre connexion à la base de données (ou choisir votre type préféré de ressource ...) une fois.

Un autre aspect non mentionné ci-dessus est le fait que chaque tentative d’accrochage a un certain impact sur la pile, ce qui peut avoir des implications pour les méthodes récursives.

Si la méthode " outer () " appelle la méthode " inner () " (qui peut s’appeler de manière récursive), essayez de localiser la méthode try-catch in "outer ()". si possible. Un simple " crash de pile " Par exemple, nous utilisons une classe de performance qui échoue à environ 6 400 images lorsque try-catch est dans la méthode interne et à environ 11 600 quand il est dans la méthode externe.

Dans le monde réel, cela peut poser problème si vous utilisez le modèle Composite et que vous avez des structures imbriquées complexes et de grande taille.

Si vous voulez intercepter Exception à chaque itération ou vérifier à quelle itération Exception est levée et intercepter toutes les Exceptions d'une itération, placez try ... catch à l'intérieur de la boucle. Cela ne cassera pas la boucle si une exception se produit et vous pouvez intercepter chaque exception à chaque itération de la boucle.

Si vous souhaitez interrompre la boucle et examiner l’exception chaque fois que vous le lancez, utilisez try ... catch out of the loop. Cela cassera la boucle et exécutera les instructions après catch (le cas échéant).

Tout dépend de vos besoins. Je préfère utiliser try ... catch dans la boucle lors du déploiement, car si Exception se produit, les résultats ne sont pas ambigus et la boucle ne se cassera pas et ne sera pas exécutée complètement.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top