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


83

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

Оскільки Java не має goto.


10
@harigm: Чи можете ви навести приклад виду коду, який ви б написали, якщо goto він доступний на Java? Я думаю, що в цьому полягає важливіше питання.
полігенмастильні матеріали

3
@harigm: Я другий поліген, не могли б ви оновити своє запитання, включивши, чому вашій програмі потрібен goto?
Усі

13
Люди завжди говорять про те, щоб ніколи не використовувати goto, але я думаю, що існує справді хороший варіант використання реального світу, який досить добре відомий і використовується .. Тобто, обов’язково виконайте якийсь код перед поверненням із функції .. Зазвичай його випуск замки чи ні, але в моєму випадку я хотів би мати можливість перейти до перерви безпосередньо перед поверненням, щоб я міг зробити необхідне обов'язкове очищення. Звичайно, програма, завалена всюди goto, була б жахливою, але якщо обмежуватись тілами методів, це не здається настільки поганим, поки ви дотримуєтеся домовленості (лише переходьте до кінця функцій, ніколи не створюйте резервні копії)
Метт Wolfe

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

7
@MattWolfe не намагається нарешті виконати роботу в цьому прикладі?
Андрес Ріофріо,

Відповіді:


83

Ви можете використати позначений 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.


88
Це не врятує вас від хижаків!
Боббі

25
Чому вам не потрібна функція goto у правильно розробленому коді?
rogermushroom

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

1
@whatsthebeef Ось відомий лист Едсгера В. Дейкстри 1968 року, який пояснює, чому він вважав, що гото шкідливий .
thomanski


42

У gotoJava немає прямого еквівалента концепції. Є кілька конструкцій , які дозволяють зробити деякі з речей , які ви можете зробити з класичним goto.

  • Оператори breakand continueдозволяють вам вистрибнути з блоку в циклі або переключити оператор.
  • Позначений оператор і break <label>дозволяє вискакувати з довільного складеного оператора на будь-який рівень у межах заданого методу (або блоку ініціалізації).
  • Якщо ви позначаєте оператор циклу, ви можете continue <label>продовжити наступну ітерацію зовнішнього циклу з внутрішнього циклу.
  • Кидання та ловлення винятків дозволяє (ефективно) вистрибнути з багатьох рівнів виклику методу. (Однак винятки відносно дорогі і вважаються поганим способом зробити "звичайний" потік управління 1. )
  • І звичайно, є return.

Жодна з цих конструкцій Java не дозволяє розгалужуватись назад або до точки в коді на тому ж рівні вкладеності, що і поточний оператор. Всі вони вистрибують з одного або декількох рівнів вкладеності (розмаху), і всі (крім continue) стрибають вниз. Це обмеження допомагає уникнути синдрому "коду спагетті", властивого старому коду 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!

Чи потрібно додавати «не використовувати!»?


1
Я не буду користуватися цим, його додатковий
gmhk

1
Помилка: Math.random()може повернути 0. Має бути> =
poitroae

Так добре. Все, що може бути реалізоване лише за допомогою хакерства байт-кодів, насправді не є Java ...
Стівен С,

Чи є щось подібне, що підтримує мітки замість номерів рядків?
Behrouz.M

1
Посилання на реалізацію порушено.
LarsH

17

Хоча деякі коментатори та прихильники аргументують, що це не 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  ..

Як виконується / під час стрибка назад? Він все ще просто виривається з блоку. Приклад лічильника: label: do {System.out.println ("Назад"); продовжити мітку;} while (false); Я думаю, у вашому прикладі while (true) - це те, що відповідає за стрибок назад.
Ben Holland

@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's - я припускаю, ви вже знаєте відповідь на це.


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

Блок нарешті завжди виконується, коли виходить блок спроби. Це гарантує, що блок нарешті виконується, навіть якщо трапляється несподіване виняток. Але нарешті корисний не лише для обробки винятків - це дозволяє програмісту уникнути випадкового обходу коду очищення шляхом повернення, продовження чи злому. Внесення коду очищення в блок нарешті - це завжди хороша практика, навіть коли не передбачаються винятки.

Дурне запитання інтерв’ю, пов’язане з нарешті, полягає в наступному: Якщо ви повертаєтесь із блоку try {}, але також маєте повернення у своєму нарешті {}, яке значення повертається?


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

Використовуйте позначену перерву як альтернативу goto.


Ця відповідь є і завжди була зайвою
Stephen C

-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

Перехід назад за допомогою продовження

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

Це призведе до нескінченного циклу, оскільки кожного разу, коли рядок continue beforeвиконується, потік коду починатиметься зновуbefore .


2
Ваш приклад про стрибок назад, використовуючи продовження, неправильний. Не можна використовувати "продовжити" поза тілом циклу, це спричиняє помилку компіляції. Однак всередині циклу, продовжуйте, просто стрибає цикл умовно. Отже, щось на зразок while (true) {continue;} було б нескінченним циклом, але так само і while (true) {}.
Ben Holland
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.