Должен ли попробовать ...catch перейти внутрь или за пределы цикла?

StackOverflow https://stackoverflow.com/questions/141560

Вопрос

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

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, возможно, внутри его собственного метода.Тем не менее, до сих пор ведутся споры о том, что быстрее.Может ли кто-нибудь проверить это и вернуться с единым ответом?

Это было полезно?

Решение 3

Хорошо, после Джеффри Л. Уитледж сказал поскольку разницы в производительности не было (по состоянию на 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 (У меня не было доступа к другим версиям).Общая разница во времени составляет порядка нескольких миллисекунд за весь тест.

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

Другие советы

Производительность:

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

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

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

Производительность:как Джеффри сказал в своем ответе, что в 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;
    }
}

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

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 в while):

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

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

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

try / catch in пока сохраняет цикл активным

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

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

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

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

С другой стороны, если вы хотите, чтобы он просто игнорировал проблемы и анализировал любые строки, которые он может, вы бы поместили 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;
}

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

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

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

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

В ваших примерах нет функциональной разницы.Я нахожу ваш первый пример более читабельным.

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

С другой стороны, вам, вероятно, следует взглянуть на float.Попробуйте проанализировать или преобразовать.toFloat .

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

С моей точки зрения, блоки try / catch необходимы для обеспечения правильной обработки исключений, но создание таких блоков влияет на производительность.Поскольку циклы содержат интенсивные повторяющиеся вычисления, не рекомендуется помещать блоки try / catch внутри циклов.Кроме того, кажется, что там, где возникает это условие, часто перехватывается "Exception" или "RuntimeException".Следует избегать обнаружения исключения RuntimeException в коде.Опять же, если вы работаете в крупной компании, важно правильно зарегистрировать это исключение или остановить возникновение исключения во время выполнения.Весь смысл этого описания в том, что PLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPS

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

в зависимости от количества итераций разница в производительности, скорее всего, будет незначительной.

Однако я согласен с другими, что наличие его вне цикла делает тело цикла более чистым.

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

Если это внутри, то вы получите накладные расходы на структуру try / catch N раз, в отличие от только одного раза снаружи.


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

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

положи его внутрь.Вы можете продолжить обработку (если хотите) или выдать полезное исключение, которое сообщает клиенту значение myString и индекс массива, содержащего неверное значение.Я думаю, что NumberFormatException уже сообщит вам неверное значение, но принцип заключается в том, чтобы поместить все полезные данные в исключения, которые вы создаете.Подумайте о том, что было бы вам интересно в отладчике на данном этапе работы программы.

Рассмотреть:

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

В случае необходимости вы по достоинству оцените подобное исключение, содержащее как можно больше информации.

Я хотел бы добавить свой собственный 0.02c о двух конкурирующих соображениях при рассмотрении общей проблемы размещения обработки исключений:

  1. Чем "шире" ответственность try-catch блок (т.е.вне цикла в вашем случае) означает, что при изменении кода на более позднем этапе вы можете ошибочно добавить строку, которая обрабатывается вашим существующим catch блок;возможно, непреднамеренно.В вашем случае это менее вероятно, потому что вы явно улавливаете NumberFormatException

  2. Чем "уже" ответственность try-catch блок, тем сложнее становится рефакторинг.Особенно, когда (как в вашем случае) вы выполняете "нелокальную" инструкцию изнутри catch блок (the return null заявление).

Это зависит от обработки сбоя.Если вы просто хотите пропустить элементы ошибки, попробуйте внутри:

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

В любом другом случае я бы предпочел попробовать снаружи.Код становится более читабельным, он более чистый.Возможно, было бы лучше вместо этого выдать исключение IllegalArgumentException в случае ошибки, если возвращается null.

Я вложу свои 0,02 доллара.Иногда вам приходится добавлять "наконец" позже в свой код (потому что кто когда-либо пишет свой код идеально с первого раза?).В таких случаях внезапно становится более разумным иметь try / catch вне цикла.Например:

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

Потому что, если вы получите сообщение об ошибке или нет, вы захотите отключить подключение к базе данных (или выбрать ваш любимый тип другого ресурса ...) только один раз.

Другим аспектом, не упомянутым выше, является тот факт, что каждая попытка улова имеет некоторые влияние на стек, которое может иметь последствия для рекурсивных методов.

Если метод "outer()" вызывает метод "inner()" (который может вызывать сам себя рекурсивно), попробуйте найти try-catch в методе "outer()", если это возможно.Простой пример "сбоя стека", который мы используем в классе производительности, завершается сбоем примерно на 6400 кадрах, когда try-catch находится во внутреннем методе, и примерно на 11 600, когда он находится во внешнем методе.

В реальном мире это может быть проблемой, если вы используете составной шаблон и имеете большие, сложные вложенные структуры.

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

Если вы хотите разорвать цикл и проверять исключение при каждом его возникновении, используйте try ...catch вне цикла.Это разорвет цикл и выполнит инструкции после catch (если таковые имеются).

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top