Философия Java

         

Имеющий дурную славу “goto”


Ключевое слово goto существовало в языках программирования с самого начала. Несомненно, goto было рождено из ассемдлерных языков программирования: “Если условие A, то перейти сюда, в противном случае, перейти сюда”. Если вы читаете ассемблерный код, который в конце концов генерируется практически каждым компилятором, вы увидите, что такое управление программой содержит много переходов. Однако goto - это переход на уровне исходного кода, и это то, что снискало дурную славу. Если программа будет всегда перепрыгивать из одного места в другое, то будет ли способ реорганизовать код так, чтобы управление не было таким прыгающим? goto попал в немилость после известной газетной публикации “Goto considered harmful” Edsger Dijkstra, и с тех пор избиение goto было популярным занятием.

Как обычно в такой ситуации середина наиболее плодотворна. Проблема не в использовании goto, а в перегрузке операторами goto — в редких ситуациях goto действительно лучший способ для структурирования управления течением прораммы.

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

Метка - это идентификатор, за которым следует двоеточие, например:

label1:

Только водном месте в Java метки полезны: прямо перед итерационными инструкциями. А сразу перед означает, что нехорошо помещать любые другие инструкции между меткой и итерацией. И единственная причина помещать метку перед итерацией есть в том случае, если вы заходите в группу другой итерации или внутри есть переключатель. Это потому, что ключевые слова break и continue обычно прерывают только текущий цикл, но когда вы используете метку, они первут внешний цикл, где стоит метка:

label1: outer-iteration { inner-iteration { //...

break; // 1


//...

continue; // 2

//...

continue label1; // 3

//...

break label1; // 4

} }



В случае 1, break прерывает внутреннюю итерацию и вы выходите во внешнюю итерацию. В случие 2, continue перемещает к началу внутренней итерации. Но в случае 3, continue label1 прерывает внутреннюю итерацию и внешнюю итерацию, все пути ведут к label1. Затем фактически продолжаются итерации, но начиная со внешней итерации. В случае 4, break label1 также прерывает все пути к метке label1, но не происходит повторного входа в итерацию. Реально происходит прерывание обеих итераций.

Вот пример использования цикла for:

//: c03:LabeledFor.java

// "Помеченный цикл for" в Java.

public class LabeledFor { public static void main(String[] args) { int i = 0; outer: // Здесь не может быть инструкций

for(; true ;) { // бесконечный цикл

inner: // Здесь не может быть инструкций

for(; i < 10; i++) { prt("i = " + i); if(i == 2) { prt("continue"); continue; } if(i == 3) { prt("break"); i++; // В противном случае i никогда

// не получит инкремент.

break; } if(i == 7) { prt("continue outer"); i++; // В противном случае i никогда

// не получит инкремент.

continue outer; } if(i == 8) { prt("break outer"); break outer; } for(int k = 0; k < 5; k++) { if(k == 3) { prt("continue inner"); continue inner; } } } } // Здесь нельзя использовать break или continue

// с меткой

} static void prt(String s) { System.out.println(s); } } ///:~

Здесь используется метод prt( ), который был использован в других примерах.

Обратите внимание, что break прерывает цикл for, и при этом не происходит инкрементации, пока не будет завершен проход цикла for. Так как break пропускает выражение инкремента, инкремент выполняется прямо в случае i == 3. Инструкция continue outer в случае i == 7 также переходит к началу цикла и также пропускает инкремент, так что нужно инкрементировать в ручную.

Вот результат работы:

i = 0 continue inner i = 1 continue inner i = 2 continue



i = 3 break

i = 4 continue inner i = 5 continue inner i = 6 continue inner i = 7 continue outer i = 8 break outer

Если не использовать инструкцию break outer, то нет способа выйти во внешний цикл из внутреннего цикла, так как break сам по себе прерывает только самый внутренний цикл. (То же самое верно и для continue.)

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

Вот демонстрация использования помеченных инструкций break и continue с циклом while:

//: c03:LabeledWhile.java

// "Помеченный цикл while" в Java.

public class LabeledWhile { public static void main(String[] args) { int i = 0; outer: while(true) { prt("Outer while loop"); while(true) { i++; prt("i = " + i); if(i == 1) { prt("continue"); continue; } if(i == 3) { prt("continue outer"); continue outer; } if(i == 5) { prt("break"); break; } if(i == 7) { prt("break outer"); break outer; } } } } static void prt(String s) { System.out.println(s); } } ///:~

Те же правила применимы для while:

  • Обычный continue переводит в начало самого внутреннего цикла и продолжает выполнение.


  • Помеченный continue переходит к метке и вновь входит в цикл, расположенный сразу за этой меткой.


  • break “выбрасывает в низ” цикла.


  • Помеченный break выбрасывает в низ после конца цикла, у которого объявлена метка.


  • Вывод этого метода становится достаточно ясным:

    Outer while loop i = 1 continue

    i = 2 i = 3 continue outer Outer while loop i = 4 i = 5 break

    Outer while loop i = 6 i = 7 break outer

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

    В газетной статье Dijkstra “Goto considered harmful”, то, против чего он действительно возражал - это метки, а не goto. Он заметил, что число ошибок увеличивается с увеличением числа меток в программе. Метки и переходы делают программу трудной для статического анализа, так как это вводит в программу циклы графов исполнения. Обратите внимание, что метки Java не испытывают этой проблемы, так как они ограничены своим местом и не могут быть использованы для передачи управления другим образом. Также интересно заметить, что это тот случай, когда особенности языка становятся более полезными при ограничении инструкции.


    Содержание раздела