Альтернатива оператору goto в Java


84

Какая альтернативная функция для ключевого слова goto в Java?

Поскольку в Java нет goto.


10
@harigm: Вы можете привести пример кода, который вы бы написали, если goto он доступен на Java? Я думаю, что в этом заключается более важный вопрос.
polygenelubricants

3
@harigm: я второй полиген, не могли бы вы обновить свой вопрос, чтобы указать, почему вашей программе нужен goto?
Все

13
Люди всегда говорят о том, чтобы никогда не использовать goto, но я думаю, что есть действительно хороший пример использования в реальном мире, который довольно хорошо известен и используется ... То есть, убедитесь, что вы выполнили некоторый код перед возвратом из функции ... Обычно его освобождение замки или что-то еще, но в моем случае я бы хотел иметь возможность прыгнуть на перерыв прямо перед возвращением, чтобы я мог выполнить требуемую обязательную очистку. Конечно, программа, заваленная повсюду goto, была бы ужасной, но если ограничиваться телами методов, это не кажется таким уж плохим, если вы следуете соглашению (только переходите к концу функций, никогда не выполняйте резервное копирование)
Мэтт Wolfe

7
Вы в хорошей компании. Линус Торвальдс страстно защищал goto kerneltrap.org/node/553 . Несомненно, самое заметное упущение в Project Coin.
Флетч

7
@MattWolfe не пытается-наконец выполнить свою работу в этом примере?
Андрес Риофрио,

Ответы:


84

Вы можете использовать пометку BREAK :

search:
    for (i = 0; i < arrayOfInts.length; i++) {
        for (j = 0; j < arrayOfInts[i].length; j++) {
            if (arrayOfInts[i][j] == searchfor) {
                foundIt = true;
                break search;
            }
        }
    }

Однако в правильно спроектированном коде функциональность GOTO не требуется.


89
Это не спасет от хищников!
Бобби

25
Почему вам не нужны функции goto в правильно разработанном коде?
rogermushroom

13
Потому что только ассемблер может его использовать. Операторы while, for, do-while, foreach, вызовы функции - все это операторы GOTO, используемые управляемым и предсказуемым образом. На уровне ассемблера это всегда будут GOTO, но вам не нужны функциональные возможности, которые дает чистый GOTO - его единственное преимущество перед функциями и операторами цикла / управления состоит в том, что он позволяет вам переходить в середину любого кода в любом месте. . И это «преимущество» само по себе является определением «спагетти-кода».

1
@whatsthebeef Вот знаменитое письмо Эдсгера В. Дейкстры 1968 года, в котором объясняется, почему он считал goto вредным .
thomanski


42

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

  • Операторы breakи continueпозволяют выйти из блока в цикле или операторе переключения.
  • Помеченный оператор, break <label>позволяющий перейти из произвольного составного оператора на любой уровень в рамках данного метода (или блока инициализатора).
  • Если вы пометите оператор цикла, вы можете continue <label>продолжить следующую итерацию внешнего цикла из внутреннего цикла.
  • Создание и перехват исключений позволяет (эффективно) выпрыгивать из многих уровней вызова метода. (Однако исключения являются относительно дорогими и считаются плохим способом выполнения «обычного» потока управления 1. )
  • И, конечно, есть return.

Ни одна из этих конструкций Java не позволяет выполнить переход назад или к точке кода на том же уровне вложенности, что и текущий оператор. Все они выпрыгивают из одного или нескольких уровней вложенности (области действия), и все (кроме continue) прыгают вниз. Это ограничение помогает избежать синдрома «спагетти-кода» goto, присущего старым кодам BASIC, FORTRAN и COBOL 2 .


1- Самая дорогая часть исключений - это фактическое создание объекта исключения и его трассировки стека. Если вам действительно действительно нужно использовать обработку исключений для «нормального» управления потоком, вы можете либо предварительно выделить / повторно использовать объект исключения, либо создать собственный класс исключения, который переопределяет fillInStackTrace()метод. Обратной стороной является то, что исключениеprintStackTrace() методы не дадут вам полезной информации ... если вам когда-нибудь понадобится их вызвать.

2. Синдром спагетти-кода породил подход структурированного программирования , при котором вы ограничиваете использование доступных языковых конструкций. Это можно было применить к BASIC , Fortran и COBOL , но это требовало осторожности и дисциплины. Полное избавление от этого gotoбыло прагматически лучшим решением. Если вы сохраните это на языке, всегда найдется какой-нибудь клоун, который будет злоупотреблять этим.


Конструкции цикла, такие как while (...) {} и for (...) {}, позволяют повторять блоки кода без явного перехода в произвольное место. Кроме того, вызовы метода myMethod () позволяют выполнять код, найденный в другом месте, и возвращаться к текущему блоку по завершении. Все это можно использовать для замены функциональности goto , предотвращая при этом общую проблему невозможности возврата, которую позволяет goto.
Крис Нава,

@Chris - это правда, но большинство языков, поддерживающих GOTO, также имеют более близкие аналоги конструкциям цикла Java.
Stephen C

1
@Chris - классический FORTRAN имеет циклы for, а классический COBOL также имеет циклические конструкции (хотя я не могу вспомнить детали). Только классический BASIC не имеет явных циклических конструкций ...
Стивен К.

31

Ради удовольствия, вот реализация GOTO на Java.

Пример:

   1 public class GotoDemo {
   2     public static void main(String[] args) {
   3         int i = 3;
   4         System.out.println(i);
   5         i = i - 1;
   6         if (i >= 0) {
   7             GotoFactory.getSharedInstance().getGoto().go(4);
   8         }
   9         
  10         try {
  11             System.out.print("Hell");
  12             if (Math.random() > 0) throw new Exception();            
  13             System.out.println("World!");
  14         } catch (Exception e) {
  15             System.out.print("o ");
  16             GotoFactory.getSharedInstance().getGoto().go(13);
  17         }
  18     }
  19 }

Запускаем это:

$ java -cp bin:asm-3.1.jar GotoClassLoader GotoDemo           
   3
   2
   1
   0
   Hello World!

Нужно ли мне добавлять «не использовать!»?


2
Ошибка: Math.random()может возвращать 0. Должно быть> =
poitroae

Да хорошо. Все, что может быть реализовано только с помощью взлома байт-кода, на самом деле не является Java ...
Стивен К.

Есть ли что-то подобное, что поддерживает метки вместо номеров строк?
Behrouz.M

1
Ссылка на реализацию не работает.
LarsH

@poitroae Это забавное пасхальное яйцо, которое кто-то найдет HellWorld!o World!выходом и придет в замешательство. #AllBugsAreFeatures
ReinstateMonica3167040

18

Хотя некоторые комментаторы и отрицательные голоса утверждают, что это не goto , сгенерированный байт-код из приведенных ниже операторов Java действительно предполагает, что эти операторы действительно выражают семантику goto .

В частности, do {...} while(true);цикл во втором примере оптимизирован компиляторами Java, чтобы не оценивать условие цикла.

Прыжок вперед

label: {
  // do stuff
  if (check) break label;
  // do more stuff
}

В байт-коде:

2  iload_1 [check]
3  ifeq 6          // Jumping forward
6  ..

Прыжок назад

label: do {
  // do stuff
  if (check) continue label;
  // do more stuff
  break label;
} while(true);

В байт-коде:

 2  iload_1 [check]
 3  ifeq 9
 6  goto 2          // Jumping backward
 9  ..

Как do / while прыгает назад? Он все равно просто вылезает из блока. Пример счетчика: label: do {System.out.println ("Backward"); метка продолжения;} while (false); Я думаю, что в вашем примере while (true) - это то, что отвечает за прыжок назад.
Бен Холланд

@BenHolland: continue labelпрыжок назад
Лукас Эдер

Я все еще не уверен. Оператор continue пропускает текущую итерацию цикла for, while или do-while. Продолжение переходит к концу тела цикла, которое затем оценивается для логического выражения, управляющего циклом. Время - это то, что прыгает назад, а не продолжение. См. Docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html
Бен Холланд,

@BenHolland: Технически ты прав. С логической точки зрения, если // do stuffи // do more stuffявляются интересующими утверждениями, continue label "имеет эффект" прыжка назад ( while(true)конечно, из-за утверждения). Я не знал, что это был такой точный вопрос ...
Лукас Эдер

2
@BenHolland: Я обновил ответ. Преобразуется while(true)компилятором в gotoоперацию байт-кода. Как trueпостоянный литерал, компилятор может выполнять эту оптимизацию и не должен ничего оценивать. Итак, мой пример действительно - это goto, прыжок назад ...
Лукас Эдер

5

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

Вы должны находиться в рамках блока, чтобы перейти к метке:

namedBlock: {
  if (j==2) {
    // this will take you to the label above
    break namedBlock;
  }
}

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


2
Я попытался реализовать это в своем вопросе SO, который связан здесь . На самом деле это не приведет вас к «метке выше», возможно, я неверно истолковал утверждения автора, но для ясности добавлю, что это приведет вас к концу внешнего блока («помеченный» блок), который находится ниже где ты ломаешься. Следовательно, вы не можете «сломаться» вверх. По крайней мере, я так понимаю.
NameSpace

4
public class TestLabel {

    enum Label{LABEL1, LABEL2, LABEL3, LABEL4}

    /**
     * @param args
     */
    public static void main(String[] args) {

        Label label = Label.LABEL1;

        while(true) {
            switch(label){
                case LABEL1:
                    print(label);

                case LABEL2:
                    print(label);
                    label = Label.LABEL4;
                    continue;

                case LABEL3:
                    print(label);
                    label = Label.LABEL1;
                    break;

                case LABEL4:
                    print(label);
                    label = Label.LABEL3;
                    continue;
            }
            break;
        }
    }

    public final static void print(Label label){
        System.out.println(label);
    }

Это приведет к исключению StackOverFlowException, если вы запустите его достаточно долго, поэтому мне нужно перейти.
Кевин Паркер

3
@Kevin: Как это приведет к переполнению стека? В этом алгоритме нет рекурсии ...
Лукас Эдер

1
Это очень похоже на исходное доказательство того, что любую программу можно написать без использования "goto" - превратив его в цикл while с переменной "label", которая в десять раз хуже, чем любой goto.
gnasher729

3

StephenC пишет:

Есть две конструкции, которые позволяют вам делать некоторые из вещей, которые вы можете делать с классическим goto.

Еще один...

Мэтт Вулф пишет:

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

try {
    // do stuff
    return result;  // or break, etc.
}
finally {
    // clean up before actually returning, even though the order looks wrong.
}

http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html

Блок finally всегда выполняется при выходе из блока try. Это гарантирует выполнение блока finally даже в случае возникновения непредвиденного исключения. Но наконец, он полезен не только для обработки исключений - он позволяет программисту избежать случайного обхода кода очистки посредством возврата, продолжения или прерывания. Помещение кода очистки в блок finally всегда является хорошей практикой, даже если не ожидается никаких исключений.

Глупый вопрос на собеседовании, связанный с finally, звучит так: если вы вернетесь из блока try {}, но также получите результат в своем finally {}, какое значение будет возвращено?


2
Я не согласен с тем, что это глупый вопрос для интервью. Опытный Java-программист должен знать, что происходит, хотя бы для того, чтобы понять, почему вставлять returnв finallyблок - плохая идея. (И даже если знание не является строго необходимым, я бы побеспокоился о программисте, у которого не было бы любопытства узнать ...)
Стивен С.

1

Самый простой:

int label = 0;
loop:while(true) {
    switch(state) {
        case 0:
            // Some code
            state = 5;
            break;

        case 2:
            // Some code
            state = 4;
            break;
        ...
        default:
            break loop;
    }
}

0

Попробуйте приведенный ниже код. Меня устраивает.

for (int iTaksa = 1; iTaksa <=8; iTaksa++) { // 'Count 8 Loop is  8 Taksa

    strTaksaStringStar[iCountTaksa] = strTaksaStringCount[iTaksa];

    LabelEndTaksa_Exit : {
        if (iCountTaksa == 1) { //If count is 6 then next it's 2
            iCountTaksa = 2;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 2) { //If count is 2 then next it's 3
            iCountTaksa = 3;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 3) { //If count is 3 then next it's 4
            iCountTaksa = 4;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 4) { //If count is 4 then next it's 7
            iCountTaksa = 7;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 7) { //If count is 7 then next it's 5
            iCountTaksa = 5;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 5) { //If count is 5 then next it's 8
            iCountTaksa = 8;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 8) { //If count is 8 then next it's 6
            iCountTaksa = 6;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 6) { //If count is 6 then loop 1  as 1 2 3 4 7 5 8 6  --> 1
            iCountTaksa = 1;
            break  LabelEndTaksa_Exit;
        }
    }   //LabelEndTaksa_Exit : {

} // "for (int iTaksa = 1; iTaksa <=8; iTaksa++) {"


-1

В Java этого нет goto, потому что он делает код неструктурированным и нечетким для чтения. Однако вы можете использовать breakи continueкак цивилизованную форму goto без проблем.


Прыжок вперед с перерывом -

ahead: {
    System.out.println("Before break");
    break ahead;
    System.out.println("After Break"); // This won't execute
}
// After a line break ahead, the code flow starts from here, after the ahead block
System.out.println("After ahead");

Выход :

Before Break
After ahead

Переход назад с помощью continue

before: {
    System.out.println("Continue");
    continue before;
}

Это приведет к бесконечной петле , как каждый раз , когда линия continue beforeвыполнена, поток кода будет снова начать сbefore .


2
Ваш пример прыжка назад с использованием continue неверен. Вы не можете использовать "continue" вне тела цикла, это вызовет ошибку компиляции. Однако внутри цикла continue просто выполняет переход по условию цикла. Таким образом, что-то вроде while (true) {continue;} будет бесконечным циклом, как и while (true) {} в этом отношении.
Бен Холланд
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.