Стоит ли пытаться ... поймать иди внутри или снаружи петли? - PullRequest
171 голосов
/ 26 сентября 2008

У меня есть цикл, который выглядит примерно так:

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

Это основное содержание метода, единственной целью которого является возвращение массива с плавающей точкой. Я хочу, чтобы этот метод возвращал null в случае ошибки, поэтому я помещаю цикл в блок try...catch, например:

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

Но потом я также подумал о том, чтобы поместить блок try...catch в цикл, например:

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

Есть ли какая-либо причина, производительность или иное, чтобы отдать предпочтение одному над другим?


Редактировать: Похоже, консенсус заключается в том, что было бы удобнее поместить цикл внутрь try / catch, возможно, внутри собственного метода. Тем не менее, есть еще споры о том, что быстрее. Может кто-нибудь проверить это и вернуться с единым ответом?

Ответы [ 21 ]

122 голосов
/ 27 сентября 2008

ПРОИЗВОДИТЕЛЬНОСТЬ:

Абсолютно нет разницы в производительности при размещении структур try / catch. Внутренне они реализованы в виде таблицы диапазона кода в структуре, которая создается при вызове метода. Пока метод выполняется, структуры try / catch полностью не видны, если только не произойдет выброс, тогда место ошибки сравнивается с таблицей.

Вот ссылка: http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

Таблица описана примерно на полпути вниз.

68 голосов
/ 26 сентября 2008

Производительность : как сказал Джеффри , в Java это не имеет большого значения.

Обычно , для читабельности кода, ваш выбор, где перехватывать исключение, зависит от того, хотите ли вы, чтобы цикл продолжал обработку или нет.

В вашем примере вы вернулись после ловли исключения. В этом случае я бы поставил try / catch вокруг цикла. Если вы просто хотите поймать плохое значение, но продолжите обработку, поместите его внутрь.

Третий способ : Вы всегда можете написать свой собственный статический метод ParseFloat и иметь дело с обработкой исключений в этом методе, а не в цикле. Делаем обработку исключений изолированной от самого цикла!

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;
    }
}
46 голосов
/ 29 сентября 2008

Хорошо, после Джеффри Л. Уитледж сказал , что разницы в производительности не было (по состоянию на 1997 г.), я пошел и протестировал ее. Я провел этот небольшой тест:

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

Я проверил полученный байт-код с помощью javap, чтобы убедиться, что ничего не вставлено.

Результаты показали, что при условии незначительной оптимизации JIT, Джеффри является правильным ; на Java 6 нет абсолютно никакой разницы в производительности , у клиента Sun * VM (у меня не было доступа к другим версиям). Общая разница во времени составляет порядка нескольких миллисекунд за весь тест.

Поэтому единственное соображение - это то, что выглядит наиболее чистым. Я считаю, что второй путь уродлив, поэтому я буду придерживаться либо первого, либо пути Рэя Хейса .

13 голосов
/ 30 октября 2015

Хотя производительность может быть одинаковой, а то, что «выглядит» лучше, очень субъективно, функциональность по-прежнему довольно велика. Возьмите следующий пример:

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

Цикл while находится внутри блока try catch, переменная 'j' увеличивается до 40, выводится, когда j mod 4 равно нулю, и выдается исключение, когда j достигает 20.

Перед любыми подробностями, вот другой пример:

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

Та же логика, что и выше, с той лишь разницей, что блок try / catch теперь находится внутри цикла while.

Здесь выводится (в то время как в try / catch):

4
8
12 
16
in catch block

И другой вывод (try / catch in while):

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

Там у вас есть довольно существенная разница:

в то время как в try / catch вырывается из цикла

пробовать / ловить, пока петля активна

13 голосов
/ 01 октября 2008

Я согласен со всеми постами производительности и читабельности. Однако есть случаи, когда это действительно имеет значение. Несколько других людей упомянули это, но это может быть легче увидеть на примерах.

Рассмотрим этот слегка измененный пример:

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

Если вы хотите, чтобы метод parseAll () возвращал значение NULL, если есть какие-либо ошибки (как в оригинальном примере), вы бы поместили try / catch снаружи следующим образом:

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

На самом деле, вам, вероятно, следует возвращать здесь ошибку, а не ноль, и, как правило, мне не нравится множественные возвраты, но вы поняли идею.

С другой стороны, если вы хотите, чтобы он просто игнорировал проблемы и анализировал все возможные строки, вы бы поместили try / catch внутри цикла следующим образом:

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;
}
5 голосов
/ 13 февраля 2009

Как уже упоминалось, производительность такая же. Однако пользовательский опыт не обязательно идентичен. В первом случае вы быстро потерпите неудачу (то есть после первой ошибки), однако, если вы поместите блок try / catch в цикл, вы можете зафиксировать все ошибки, которые будут созданы для данного вызова метода. При разборе массива значений из строк, где вы ожидаете ошибок форматирования, определенно есть случаи, когда вы хотели бы иметь возможность представить все ошибки пользователю, чтобы им не нужно было пытаться исправить их по одному .

4 голосов
/ 31 августа 2012

До тех пор, пока вы знаете, что вам нужно выполнить в цикле, вы можете поместить попытку try вне цикла. Но важно понимать, что цикл закончится, как только произойдет исключение, и это не всегда может быть тем, что вы хотите. На самом деле это очень распространенная ошибка в программном обеспечении на основе Java. Людям необходимо обработать несколько элементов, таких как очистка очереди, и ложно полагаться на внешний оператор try / catch, обрабатывающий все возможные исключения. Они также могут обрабатывать только определенное исключение внутри цикла и не ожидать появления какого-либо другого исключения. Затем, если возникает исключение, которое не обрабатывается внутри цикла, тогда цикл будет «предопределенным», он может завершиться преждевременно, и внешний оператор catch обработает исключение.

Если в жизни цикл должен был выполнять свою роль по освобождению очереди, то этот цикл, скорее всего, мог бы закончиться до того, как эта очередь была действительно очищена. Очень распространенная ошибка.

4 голосов
/ 26 сентября 2008

Если происходит сбой "все или ничего", тогда имеет смысл использовать первый формат. Если вы хотите иметь возможность обрабатывать / возвращать все исправные элементы, вам нужно использовать вторую форму. Это были бы мои основные критерии для выбора между методами. Лично, если бы это было все или ничего, я бы не использовал вторую форму.

2 голосов
/ 26 сентября 2008

Если вы поместите try / catch внутри цикла, вы будете продолжать цикл после исключения. Если вы поместите его вне цикла, вы остановитесь, как только будет сгенерировано исключение.

2 голосов
/ 26 сентября 2008

Вы должны предпочесть внешнюю версию внутренней. Это просто конкретная версия правила, переместите все, что находится за пределами цикла, что вы можете переместить за пределы цикла. В зависимости от компилятора IL и JIT ваши две версии могут иметь или не иметь разные характеристики производительности.

В другой заметке вам, вероятно, стоит взглянуть на float.TryParse или Convert.ToFloat.

...